如何借助 HealthKit 打造一款健身应用?

冷暖自知 提交于 2020-03-02 05:58:21

【编者按】本文作者为 Matthew Maher,文章手把手地介绍了如何借助 HealthKit 建立简单的健身应用,包含诸多代码实例。本文系国内 ITOM 管理平台 OneAPM 编译呈现。

根据新闻报导,健康与健美在今时今日的重要程度比已往任何时候都高。说起来有点可笑,似乎就在几天之前,笔者就见到过类似的新闻。或许,这是当人逐渐变老之后挥之不去的感觉吧——渴望保持健康以及健美的感觉。不管怎么说,健康与健美是一个重要话题。技术的进步,尤其是移动应用与硬件世界的不断提高,正为这个似乎日益成长的话题带来全新的契机。

HealthKit 是苹果公司推出的一款移动应用平台,旨在为重要、可追踪的健康数据与注重健康、热衷锻炼的科技消费者搭起桥梁。这很酷。用户可以轻松地追踪一段时间内可测量的健身与健康数据。除了了解自身的健康数据,看到图表中喜人的增长曲线也的确鼓舞人心。

正如人们想象的那样,在管理健康信息时安全是非常重要的考虑因素。HealthKit 直截了当地将所有 HealthKit 信息的绝对控制权置于用户的手中。用户可以授权或拒绝任何应用对其健康数据发出的读取请求。

作为开发者,我们需要征求许可才能从/向 HealthKit 读取/写入数据。实际上,我们需要明确地声明打算读取或改变的数据。此外,任何使用 HealthKit 的应用都必须包含隐私政策,这样一来,用户才能对其信息的处理感到更加放心。

##关于 OneHourWalker 在本文中,我们将打造一个有趣的小应用,它会从 HealthKit 读取数据,也会向其写入新数据。来见一见 OneHourWalker 吧。

OneHourWalker 是一款追踪使用者在一个小时内行走或跑步之距离的健身应用。用户可以将距离与 HealthKit 分享,之后就能在健康应用中读取之。我知道,一个小时听起来有点过于乐观了(至少笔者本人可能无法坚持下去)。因此,用户也可以提早中止计数,并分享距离。

额,到目前为止,似乎 OneHourWalker 只会向 HealthKit 写入数据。我们需要读取什么数据呢?

好问题!在步行锻炼时,我喜欢选择乡间或林间小路。常常,我会遇到树枝低垂的区域。而我是一条身高 193cm 的汉子,这真的让我很苦恼。解决办法是:从 HealthKit 读取用户的身高数据,将之打印为应用的一个标签。这个标签可以作为对用户的善意提醒,这样,他们就能避免在步行时被树枝打到。

首先,点此下载 OneHourWalker 的初始项目。先试着跑起来,找找应用运行的感觉。计数器与地点追踪功能已经在运行了,所以我们只需专注于 HealthKit 实现。注意,当到达 60 分钟时间点时,计算器与追踪都会停止。

##启用 HealthKit 首先,在我们的应用中启用 HealthKit。在项目导航中,点击 OneHourWalker,之后点击 Targets 下面的 OneHourWalker,之后选择屏幕顶部的 Capabilities 选项。

查看 Capabilities 列表的底部,启用 HealthKit。这一简单的操作会将 HealthKit 权限添加到 App ID,将 HealthKit 键添加到 info plist 文件,将 HealthKit 权限添加到授权文件,并且与 HealthKit.framework 相连接。就是这么简单。

##开始编程 接下来,跳转到 TimerViewController.swift,开始将 HealthKit 引入 OneHourWalker。首先,创建一个 HealthKitManager 实例。

import UIKit
import CoreLocation
import HealthKit

class TimerViewController: UIViewController, CLLocationManagerDelegate {

    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var milesLabel: UILabel!
    @IBOutlet weak var heightLabel: UILabel!    

    var zeroTime = NSTimeInterval()    
    var timer : NSTimer = NSTimer()

    let locationManager = CLLocationManager()    
    var startLocation: CLLocation!    
    var lastLocation: CLLocation!    
    var distanceTraveled = 0.0

    let healthManager:HealthKitManager = HealthKitManager()

所有 HealthKit 工作都会在 HealthKitManager.swift 中进行。它会包含重要的方法,我们很快就会谈到。

正如在前文介绍部分所述,我们需要取得用户的许可,才能读取并修改他们的健康数据。在 viewDidLoad()中,我们就得这么做。

    override func viewDidLoad() {
       super.viewDidLoad()

   locationManager.requestWhenInUseAuthorization()

    if CLLocationManager.locationServicesEnabled(){
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
    } else {
        print("Need to Enable Location")
    }

    // We cannot access the user's HealthKit data without specific permission.
    getHealthKitPermission()
}

getHealthKitPermission() 方法会调用 manager 的 authorizeHealthKit()方法。如果一切顺利,我们便能调用setHeight()方法。不过,我们很快会在后文中谈到此方法。

func getHealthKitPermission() {        

    // Seek authorization in HealthKitManager.swift.
    healthManager.authorizeHealthKit { (authorized,  error) -> Void in                        if authorized {                

            // Get and set the user's height.
            self.setHeight()
        } else {                
                   if error != nil {                    
                        print(error)
            }                
            print("Permission denied.")
        }
    }
}

在 HealthKitManager.swift 中,我们会创建 authorizeHealthKit() 方法。然而,除此之外,我们需要创建 HealthKit 存储,用于连接应用与 HealthKit 的数据。

let healthKitStore: HKHealthStore = HKHealthStore()

func authorizeHealthKit(completion: ((success: Bool, error: NSError!) -> Void)!) {

    // State the health data type(s) we want to read from HealthKit.        
    let healthDataToRead = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)!)

    // State the health data type(s) we want to write from HealthKit.        
    let healthDataToWrite = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)!)

    // Just in case OneHourWalker makes its way to an iPad...        
    if !HKHealthStore.isHealthDataAvailable() {            
        print("Can't access HealthKit.")
    }

    // Request authorization to read and/or write the specific data.        
    healthKitStore.requestAuthorizationToShareTypes(healthDataToWrite, readTypes: healthDataToRead) { (success, error) -> Void in            
    if( completion != nil ) {
            completion(success:success, error:error)
        }
    }
}

在请求获取用户健康数据的授权时,我们需要明确指定打算读取以及修改的信息。对本例而言,我们需要读取用户的身高,从而帮助他们躲避有危险的低垂枝丫。我们希望 HealthKit 能提供一个可以转化为可理解的身高的 HKObject 量。此外,我们还要获得修改 HKObject 量的许可,以记录用户的行走及跑步距离。

在处理好 OneHourWalker 与 iPad 通信的可能性后,我们做出官方请求。

HealthKitManager.swift 中,创建从 HealthKit 读取用户身高数据的 getHeight() 方法。

func getHeight(sampleType: HKSampleType , completion: ((HKSample!, NSError!) -> Void)!) {

    // Predicate for the height query        
    let distantPastHeight = NSDate.distantPast() as NSDate        
    let currentDate = NSDate()        
    let lastHeightPredicate = HKQuery.predicateForSamplesWithStartDate(distantPastHeight, endDate: currentDate, options: .None)

    // Get the single most recent height        
    let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)

    // Query HealthKit for the last Height entry.        
    let heightQuery = HKSampleQuery(sampleType: sampleType, predicate: lastHeightPredicate, limit: 1, sortDescriptors: [sortDescriptor]) { (sampleQuery, results, error ) -> Void in                

    if let queryError = error {
                completion(nil, queryError)                    
                return
            }                

            // Set the first HKQuantitySample in results as the most recent height.                let lastHeight = results!.first                

            if completion != nil {
                completion(lastHeight, nil)
            }
    }        

    // Time to execute the query.
    self.healthKitStore.executeQuery(heightQuery)
}

查询身高数据的第一步是创建一个断言以定义时间参数。我们是在请求一段时间内的所有身高数据——与当前日期相距甚远的一个过去的日期。显然,这会返回一个数组。然而,我们只想要最近期的身高,因此,我们请求数据时可以让最新的数据排在数组的最前头。

在构建这一查询时,我们会把数组的长度限制为1。在考虑好出现错误的可能性后,我们会将结果中的首个也即唯一一个数组项目分配给 lastHeight。接下来,完善 getHeight() 方法。最后,针对用户的健康数据执行查询。

回到 TimerViewController.swift,在 app 真正投入使用之前,假设用户授权了适当的许可,则 setHeight() 方法会被 getHealthKitPermission() 调用。

var height: HKQuantitySample?

首先,我们需要为 HKQuantitySample 实例声明一个身高变量。

func setHeight() {        
     // Create the HKSample for Height.        
     let heightSample = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)        

     // Call HealthKitManager's getSample() method to get the user's height.              
     self.healthManager.getHeight(heightSample!, completion: { (userHeight, error) -> Void in            

     if( error != nil ) {
            print("Error: \(error.localizedDescription)")                
            return
        }            

        var heightString = ""            

        self.height = userHeight as? HKQuantitySample            

        // The height is formatted to the user's locale.            
        if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) {                
            let formatHeight = NSLengthFormatter()
            formatHeight.forPersonHeightUse = true
            heightString = formatHeight.stringFromMeters(meters)
        }            

        // Set the label to reflect the user's height.
        dispatch_async(dispatch_get_main_queue(), { () -> Void in                                self.heightLabel.text = heightString
        })
    })

}

share() 方法之上,我们会创建 setHeight() 方法。我们请求的身高数据样本以 HKQuantity 返回,标识符 HKQuantityTypeIdentifierHeight 知道这一对象。

接下来,调用在 manager 中创建的 getHeight() 方法。有了身高样本,我们还需要将之翻译为恰当的字符串以展示在标签中。与往常一样,考虑所有可能的错误情况是很重要的。

到此,用户就可以打开 app,查看他们的身高(如果他的健康应用中记录着身高数据),开启计时器,追踪他跑步或行走的距离了。接下来,我们要处理将距离数据写入健康应用的过程,这样,用户才能在同一个应用中保存其所有的健身数据。

在用户结束外出锻炼之后,不管有没有到60分钟,他可能会使用 Share(分享)按钮将其辛苦赚得的运动距离发送到健康应用。所以,在 share() 方法中,我们需要调用 HealthKitManager.swiftsaveDistance() 方法来实现这一过程。在这个方法中,我们会发送运动距离以及取得该距离的日期。这样,用户便能在第二天争取更好的成绩。

@IBAction func share(sender: AnyObject) {    
      healthManager.saveDistance(distanceTraveled, date: NSDate())
}

接下来,回到 manager,我们要在此处创建 saveDistance() 方法。首先,我们要让 HealthKit 知道我们打算写入一个代表步行及跑步距离的量。之后,将度量单位设置为英里,并赋值官方的样本量。HealthKit 的 saveObject() 方法会将此数据写入用户的健康数据。

func saveDistance(distanceRecorded: Double, date: NSDate ) {        

    // Set the quantity type to the running/walking distance.
    let distanceType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)        

    // Set the unit of measurement to miles.
    let distanceQuantity = HKQuantity(unit: HKUnit.mileUnit(), doubleValue: distanceRecorded)        

    // Set the official Quantity Sample.
    let distance = HKQuantitySample(type: distanceType!, quantity: distanceQuantity, startDate: date, endDate: date)        

    // Save the distance quantity sample to the HealthKit Store.
    healthKitStore.saveObject(distance, withCompletion: { (success, error) -> Void in            
    if( error != nil ) {                
          print(error)
        } else {                
          print("The distance has been recorded! Better go check!")
        }
    })
}

跳转到健康应用,所记录的数据会出现在 Walking + Running Distance(行走+跑步距离)一行(如果已经启用)。此外,依照下面的路径,我们可以看到详细的样本数据:Health Data tab(健康数据选项卡) > Fitness(健身) > Walking + Running Distance(行走+跑步距离) > Show All Data(显示所有数据)。我们的数据就在此列表中。轻击一个单元,我们的图标(目前还未设置)就会与距离一同出现。再次点击此单元,就能看到完整的细节数据。

借助 OneHourWalker,我们便能为全世界 iOS 用户的身体健康贡献一份力量。然而,这只是一个开始。在使用 HealthKit 读取并修改健康数据的道路上,还有非常多的可能性。

当然,对用户而言,拥有这些可追踪数据的好处很多。人们可以轻松地按照日期、星期进行比较,从而激励自己朝着目标努力。不过,真正的伟大之处在于,开发者可以提供全新的,富有创造力的有趣方法来获取数据。

欢迎大家对 HealthKit 应用进行测试。点击此处查看 OneHourWalker 的最终版本。

本文系 OneAPM 工程师编译整理。OneAPM Mobile Insight真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

本文转自 OneAPM 官方博客

原文地址:http://www.appcoda.com/healthkit-introduction/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!