原文:HealthKit Tutorial with Swift: Workouts 作者:Ernesto García 译者:Mr_cyz )

欢迎回到我们的HealthKit系列教程!

在我们系列教程的第一篇中译版)中,你已经学到了使用HealthKit开发的基础:读写数据。

在这第二篇,同时也是最后一篇中,你将会学到怎么样处理一种更复杂的数据类型:锻炼与健身的信息(Workout)。

这篇教程从上一篇教程结束的地方开始,所以如果你还没有上一篇中完成的工程,你可以从这里下载

开始

单从身体方面来说,一个人的锻炼与健身信息由一段时间内做过的身体素质锻炼组成。而再加上数字化的方面来看,你可以通过下面的这些基本属性来得到一条锻炼信息:

  • 运动的种类。例如:跑步、骑行、冰壶等

  • 距离

  • 起止时间

  • 持续时间

  • 运动时消耗的能量。

数字化的领域就是指的HealthKit了,一条锻炼信息就是一个其他类型的数据采样信息(samples)的容器。例如,你可以添加一组数据,来表示在你运动时的心率。如果你打算做一款健康类型的app,那么这将是一个功能强大的特性。

在该工程中,你将会存储跑步锻炼时的信息,当然你也可以很容易地改变活动的种类来表示其他类型的锻炼信息。

我们的起始项目中已经包含了一个视图控制器,来为你提供查看健康信息的入口。导航到Workouts栏然后点击+按钮你就能看到了。

当你停止运动时,会在该页面收集信息并且展示到Workouts视图控制器中。你需要使用这些信息来创建一条健康信息。

保存锻炼信息

首先,你将创建一个方法来保存跑步时的信息。打开HealthManager.swift,添加如下方法:

func saveRunningWorkout(startDate:NSDate , endDate:NSDate , distance:Double, distanceUnit:HKUnit , kiloCalories:Double,
  completion: ( (Bool, NSError!) -> Void)!) {
 
    // 1. Create quantities for the distance and energy burned
    let distanceQuantity = HKQuantity(unit: distanceUnit, doubleValue: distance)
    let caloriesQuantity = HKQuantity(unit: HKUnit.kilocalorieUnit(), doubleValue: kiloCalories)
 
    // 2. Save Running Workout
    let workout = HKWorkout(activityType: HKWorkoutActivityType.Running, startDate: startDate, endDate: endDate, duration: abs(endDate.timeIntervalSinceDate(startDate)), totalEnergyBurned: caloriesQuantity, totalDistance: distanceQuantity, metadata: nil)
    healthKitStore.saveObject(workout, withCompletion: { (success, error) -> Void in
      if( error != nil  ) {
        // Error saving the workout
        completion(success,error)
      }
      else {
        // Workout saved
        completion(success,nil)
 
      }
    })
}

这段代码做了什么呢?让我们一行一行地分析:

  1. 与你之前创建BMI类型的身体素质信息一样,创建两个身体素质类型的对象,分别设置为距离和能量类型,注意使用double类型的数据以及选择合适的单位。

  2. 使 用起止时间、持续时间以及你刚刚创建好的代表距离和消耗的能量的身体素质信息来创建一个HKWorkOut对象,然后通过调用HKHealthKit Store的saveObject方法来将健康信息保存到Store中,结果信息或者是错误信息将传到completion回调中。

现在你需要在Workouts视图控制器中调用该方法,打开WorkoutsTableViewController.swift然后找到unwindToSegue()方法,当你在新的Workout界面按下Done的时候就会调用该方法。

将下面这一行:

println("TODO: Save workout in Health Store")

替换为:

if let addViewController:AddWorkoutTableViewController = segue.sourceViewController as? AddWorkoutTableViewController {
 
// 1. Set the Unit type
var hkUnit = HKUnit.meterUnitWithMetricPrefix(.Kilo)
if distanceUnit == .Miles {
  hkUnit = HKUnit.mileUnit()
}
 
// 2. Save the workout
self.healthManager?.saveRunningWorkout(addViewController.startDate!, endDate: addViewController.endDate!, distance: addViewController.distance , distanceUnit:hkUnit, kiloCalories: addViewController.energyBurned!, completion: { (success, error ) -> Void in
  if( success )
  {
    println("Workout saved!")
  }
  else if( error != nil ) {
    println("\(error)")
  }
})
}
  1. 首先,创建一个合适的单位对象,用户通过分段控件(segment control)设置distanceUnit的值,从而选择距离单位的类型。这段代码检查distanceUnit的值来决定使用合适的HKUnit。

  2. 创建完单位之后调用saveRunningWorkoutMethod()方法来保存这些健康信息,包括开始日期、结束日期、持续时间以及能量消耗。

编译并运行,点击 + 按钮,像下面视图中展示的那样填入数据。

哇!26.2英里(42.195千米),而且是两小时零一分钟之内,我想你刚刚在编码的过程中打破了马拉松世界记录。你真是个天才!

完成之后点击Done,如果一切顺利,你将在Xcode的控制台中看到如下信息:

Workout saved!

太棒了!你的健康信息已经成功地被保存到HealthKit Store中了,如果你想的话你可以重复刚才的操作来添加更多的健康信息。

查询健康信息

如果你运行你的应用,然后进入Workouts视图控制器,你不会看到任何之前你在视图中创建好的健康信息。

你需要加入一些代码来读取并且展示这些信息。为了能够读取到这些信息,你需要创建一个HKSampleQuery对象,然后执行这个查询来获取数据。

这与之前读取身高和体重的代码将会非常类似,为什么试着自己写一下呢?

在HealthManager.swift中创建一个方法,利用HKWorkoutActivityType类型来查询健康信息,将结果按照起始日期降序的顺序排好序,在completion回调中获取返回结果。使用如下方法声明:

func readRunningWorkOuts(completion: (([AnyObject]!, NSError!) -> Void)!)

内部实现:readRunningWorkOuts的实现:

打开HealthManager.swift然后添加这个方法:

func readRunningWorkOuts(completion: (([AnyObject]!, NSError!) -> Void)!) {
 
  // 1. Predicate to read only running workouts
  let predicate =  HKQuery.predicateForWorkoutsWithWorkoutActivityType(HKWorkoutActivityType.Running)
  // 2. Order the workouts by date
  let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
  // 3. Create the query
  let sampleQuery = HKSampleQuery(sampleType: HKWorkoutType.workoutType(), predicate: predicate, limit: 0, sortDescriptors: [sortDescriptor])
    { (sampleQuery, results, error ) -> Void in
 
      if let queryError = error {
        println( "There was an error while reading the samples: \(queryError.localizedDescription)")
      }
      completion(results,error)
  }
  // 4. Execute the query
  healthKitStore.executeQuery(sampleQuery)
 
}

这段代码与你之前读身高和体重的代码非常类似:

  1. 首 先创建一个谓词对象,HKQuery提供了一个方法:predicateForWorkoutsWithWorkoutActivityType()来创 建查询锻炼信息用到的谓词。通过使用HKWorkoutActivityType.Running参数来指定你希望查询的是跑步类型的锻炼信息。如果你想 查询其他类型的锻炼信息,例如骑行、游泳,你只需要在创建该谓词的时候改变查询类型即可。

  2. 创建一个排序标识符来让结果信息按照日期降序排列。

  3. 创建HKSampleQuery对象,调用executeQuery()方法来获得结果。

你需要将健康信息展示到列表中,所以你要调用该方法并实现UITableView的数据源协议,所以,接下来打开WorkoutsTableViewController.swift。

你需要在该类中创建一个数组类型的属性来保存所有的健康信息,将这一行代码添加到WorkoutsTableViewController顶部声明其他属性的附近:

var workouts = [HKWorkout]()

然后,添加该方法,作用是当视图刚刚出现时读取健康信息:

public override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)
 
  healthManager?.readRunningWorkOuts({ (results, error) -> Void in
    if( error != nil )
    {
      println("Error reading workouts: \(error.localizedDescription)")
      return;
    }
    else
    {
      println("Workouts read successfully!")
    }
 
    //Kkeep workouts and refresh tableview in main thread
    self.workouts = results as [HKWorkout]
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
      self.tableView.reloadData()
    });
 
  })
}

这里调用了你刚刚创建好的方法readWorkouts。当收到结果后,将结果保存到workouts中,然后在主线程中刷新列表数据。

现在,你需要添加列表的数据源协议中的方法,在WorkoutsTableViewController中添加tableView:numberOfRowsInSection方法的实现:

public override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return  workouts.count
}

这很直接明了,当列表询问有多少行时,你只需要返回你从Store中读到的健康信息的数量即可。

现在是时候填充列表的cell了,你需要实现在列表的数据源协议中的方法tableView:cellForRowAtIndexPath,将如下代码添加到WorkoutsTableViewController类中:

public override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("workoutcellid", forIndexPath: indexPath) as UITableViewCell
 
 
  // 1. Get workout for the row. Cell text: Workout Date
  let workout  = workouts[indexPath.row]
  let startDate = dateFormatter.stringFromDate(workout.startDate)
  cell.textLabel!.text = startDate
 
  // 2. Detail text: Duration - Distance 
  // Duration
  var detailText = "Duration: " + durationFormatter.stringFromTimeInterval(workout.duration)!
  // Distance in Km or miles depending on user selection
  detailText += " Distance: "
  if distanceUnit == .Kilometers {
    let distanceInKM = workout.totalDistance.doubleValueForUnit(HKUnit.meterUnitWithMetricPrefix(HKMetricPrefix.Kilo))
    detailText += distanceFormatter.stringFromValue(distanceInKM, unit: NSLengthFormatterUnit.Kilometer)
  }
  else {
    let distanceInMiles = workout.totalDistance.doubleValueForUnit(HKUnit.mileUnit())
    detailText += distanceFormatter.stringFromValue(distanceInMiles, unit: NSLengthFormatterUnit.Mile)
 
  }
  // 3. Detail text: Energy Burned 
  let energyBurned = workout.totalEnergyBurned.doubleValueForUnit(HKUnit.jouleUnit())
  detailText += " Energy: " + energyFormatter.stringFromJoules(energyBurned)
  cell.detailTextLabel?.text = detailText;
 
 
  return cell
}

解释一下上面的代码:

  1. 这段代码获得当前这一行的健康信息,然后将起始日期格式化并展示在cell中的text lable上。为了将日期格式化,这里使用了之前在初始工程中为你创建好的NSDateFormatter类。

  2. 通 过距离和消耗的能量来定义展示在detail label中的字符串。距离信息以英里或者千米的形式展示出来,这取决于用户的选择(被保存在distanceUnit属性中)。获取距离的double 类型的值,然后基于该属性的值传入一个合适的距离单位。接下来,使用NSDistanceFormatter类,传入合适的单位,调用 stringFromValue:unit方法,将距离信息格式化为本地字符串。对于该条健康信息的持续时间,使用一个 NSDateComponentsFormatter类对象。所有这些格式转换器都是在初始工程中为你预先创建好的。

  3. 使用NSEnergyFormatter将消耗的能量也转换为字符串,该字符串最终将被展示到detail label中。

编译并运行该app,导航到Workouts页面,现在你应该能在列表中看到之前储存好的健康信息了。

酷!你已经如期将所有的健康信息展示出来了。现在,点击分段控件,检验一下,距离以英里或以千米为单位来展示,取决于你的选择。

如果你打开Health应用,你不会在任何地方找到这些信息,它就是这样设计的,因为Health应用只展示数据采样信息,并不会展示我们的健康信息。

然而,你是可以让用户看到这些关于健康的信息的,你只需要将它们与一些采样信息关联起来即可。一款健康管家应用如果不带有健康与锻炼的信息,就好比一教练机没有警报装置一样。因此你应该将这些数据结合起来。

把采样信息添加到健康信息中

作为最后一步,你将把距离和消耗的能量这些采样信息添加到健康信息中。

打开HealthManager.swift然后前往saveRunningWorkout(),在成功执行的回调闭包中,将下面这两行:

// Workout saved
completion(success,nil)

替换为:

// if success, then save the associated samples so that they appear in the HealthKit
let distanceSample = HKQuantitySample(type: HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning), quantity: distanceQuantity, startDate: startDate, endDate: endDate)
let caloriesSample = HKQuantitySample(type: HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned), quantity: caloriesQuantity, startDate: startDate, endDate: endDate)
 
self.healthKitStore.addSamples([distanceSample,caloriesSample], toWorkout: workout, completion: { (success, error ) -> Void in
  completion(success, error)
})

到现在,你可能对这些代码已经非常熟悉了,甚至可能到了你会认为你在做重复的事情的地步了。但是为了让这些更加清晰,这里做一下解释:

第 一步中,创建了两个quantity sample类型的对象,一个用DistanceWalkingRunning类型来代表跑步的距离。另一个用ActiveEnergyBurned类型 来代表消耗的卡路里,然后调用HealthKit Store的方法addsamples:ToWorkout将这两个数据添加到健康信息中。

编译并运行该应用,添加一到两条健康信息,然后关掉应用。既然这些健康信息已经和距离以及消耗的能量的数据关联起来了,你就可以在Health应用中看到它们了。

打开Health应用,前往Health Data栏,在这里选择Fitness选项,然后选择Walking+Running Distance或者Active Calories来检查一下数据是否保存到那里了。你将会看到类似如下界面:

太棒了!现在你已经有获得最重要的健康信息并将其保存到HealthKit Store中的能力了。

现在该干什么?

这里你可以下载包含这篇教程中所有代码的工程。

! 重要!:如果你想使用上面的示例工程,在使用HealthKit之前需要进行一些设置,因为该工程绑定了一个示例用Bundle ID,你需要将其修改为你自己的Bundle ID,选择你的开发团队,然后将Target栏中Capabilities菜单下的HealthKit的开关由OFF变为ON。

详见上述“开始”部分和“授权与许可”部分。

但愿本篇教程能够就HealthKit的基础概念给你一些对认识与了解,让你明白怎么样在自己的应用中使用HealthKit。要了解更多关于HealthKit的相关知识,这里有一些相关资源:


浏览过这些文档和视频之后,你应该准备好前往HealthKit更深入的方面,然后对本篇的这个应用做一些改善。例如,你可以添加一些新的类型的数据采样
信息或者是健康信息,使用HKStatisticsQuery计算统计结果,或者通过HKObserverQuery来观察Store中信息的改变。

我希望你能喜欢这篇教程,和以前一样,如果你有任何问题或者评论,请参与下方的讨论!
(本文为CocoaChina组织翻译,本译文权利归译者所有,未经允许禁止转载。)

HealthKit教程 Swift版:锻炼信息的更多相关文章

  1. HealthKit开发教程Swift版:起步

    原文:HealthKit Tutorial with Swift: Getting Started 作者:Ernesto García 译者:Mr_cyz ) HealthKit是iOS 8中的新的A ...

  2. Swift版iOS游戏框架Sprite Kit基础教程下册

    Swift版iOS游戏框架Sprite Kit基础教程下册 试读下载地址:http://pan.baidu.com/s/1qWBdV0C 介绍:本教程是国内唯一的Swift版的Spritekit教程. ...

  3. 关东升的iOS实战系列图书 《iOS实战:入门与提高卷(Swift版)》已经上市

             承蒙广大读者的厚爱我的 <iOS实战:入门与提高卷(Swift版)>京东上市了,欢迎广大读者提出宝贵意见.http://item.jd.com/11766718.html ...

  4. 关东升的《iOS实战:图形图像、动画和多媒体卷(Swift版)》上市了

    关东升的<iOS实战:图形图像.动画和多媒体卷(Swift版)>上市了 承蒙广大读者的厚爱我的<iOS实战:图形图像.动画和多媒体卷(Swift版)>京东上市了,欢迎广大读者提 ...

  5. W3Cschool菜鸟教程离线版下载链接

    请在电脑上打开以下链接进行下载w3cschool 离线版(chm):http://pan.baidu.com/s/1bniwRCV(最新,2014年10月21日更新)w3cschool 离线版(htm ...

  6. Swift版音乐播放器(简化版),swift音乐播放器

    这几天闲着也是闲着,学习一下Swift的,于是到开源社区Download了个OC版的音乐播放器,练练手,在这里发扬开源精神, 希望对大家有帮助! 这个DEMO里,使用到了 AudioPlayer(对音 ...

  7. 【SIKIA计划】_04_C#中级教程 (2015版)笔记

    IKIC#中级教程 (2015版)正常模式指的是不会影响程序的正常运行.1,在VS中我们使用Console.Write(或者WriteLine)方法向控制台输出变量的值,通过这个我们可以查看变量的值是 ...

  8. Flask 教程精简版之一(系列片)

    Flask 教程精简版之一(系列片) 现在连教程都有精简版 准备 1.要学会 Flask 之前必须掌握 Python 基本使用. 2.会使用简单的 HTML 效果更加 3.若想练气功必须先自暴自弃 简 ...

  9. [译]RabbitMQ教程C#版 - “Hello World”

    [译]RabbitMQ教程C#版 - “Hello World”   先决条件本教程假定RabbitMQ已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需 ...

随机推荐

  1. BZOJ 4057: [Cerc2012]Kingdoms( 状压dp )

    状压dp.... 我已开始用递归结果就 TLE 了... 不科学啊...我dp基本上都是用递归的..我只好改成递推 , 刷表法 将全部公司用二进制表示 , 压成一个数 . 0 表示破产 , 1 表示没 ...

  2. 百度网盘自动上传脚本-bpcs_uploader

    安装jsonpear install pecl/json 一.bpcs_uploader下载和使用: 1.下载地址:http://oott123.github.com/bpcs_uploader/ 2 ...

  3. Handler.removeMessages的作用,有时候为什么一定要先remove一下呢

    removeMessages会将handler对应message queue里的消息清空,如果带了int参数则是对应的消息清空.队列里面没有消息则handler会不工作,但不表示handler会停止. ...

  4. Android核心基础(十一)

    1.Android的状态栏通知(Notification) 通知用于在状态栏显示消息,消息到来时以图标方式表示,如下: //获取通知管理器 NotificationManager mNotificat ...

  5. paip.按键替换映射总结

    paip.按键替换映射总结 作者Attilax ,  EMAIL:1466519819@qq.com  来源:attilax的专栏 地址:http://blog.csdn.net/attilax 因为 ...

  6. 简单区分`:before`与`::before`的区别

    简单区分:before与::before的区别 :hover我们都知道,称作伪类,英文名pseudo-class,而我们此处提到的:before以及:after也是伪类,属于css2的内容,在ie8下 ...

  7. python编写网络抓包分析脚本

    python编写网络抓包分析脚本 写网络抓包分析脚本,一个称手的sniffer工具是必不可少的,我习惯用Ethereal,简单,易用,基于winpcap的一个开源的软件 Ethereal自带许多协议的 ...

  8. 列举一些常见的Python HTTP服务器

    要使 Python 写的程序能在 Web 上被访问,还需要搭建一个支持 Python 的 HTTP 服务器.下面列举一些常见的 Python HTTP 服务器,以及它们目前的大致发展情况,以便用户的对 ...

  9. C# c++ 传递函数指针

    C#和c++之间相互传递函数指针 在C++和C#之中都有很多callback method,可以相互调用吗,怎么传递,是我表弟的问题. 1.定义c++ dll ,导出方法 // sort.cpp : ...

  10. 写一个函数int get(),这个函数运行一次可以从V[N]里随机取出一个数,而这个数必须是符合1/N平均分布的

    题目:有一个函数int getNum(),每运行一次可以从一个数组V[N]里面取出一个数,N未知,当数取完的时候,函数返回NULL.现在要求写一个函数int get(),这个函数运行一次可以从V[N] ...