開(kāi)發(fā)一個(gè)健身 App,用 HealthKit 來(lái)跟蹤步行距離

原文鏈接
作者:AppCoda
原文日期:2016-03-22

看新聞我們也知道,比起歷史上任何一個(gè)時(shí)刻,健身和健康在今天越來(lái)越重要了。說(shuō)起了也挺好笑的,我似乎記得幾天前新聞告訴我同樣的事情,也許是因?yàn)槟昙o(jì)越來(lái)越大的緣故,更需要健康和健身了。不管怎么說(shuō),這是一個(gè)熱門(mén)話題。隨著技術(shù)的不斷進(jìn)步,手機(jī)應(yīng)用和硬件在世界范圍內(nèi)都變得流行起來(lái),這些都把日益流行的健身健康話題加入了新的元素。

HealthKit 是蘋(píng)果公司的重要橋梁,把重要的跟蹤的健康數(shù)據(jù)同有健康意識(shí)的科技消費(fèi)者、運(yùn)動(dòng)迷、平常使用 iPhone 的人連接了起來(lái)。這很酷,用戶(hù)可以很容易的就追蹤衡量一段時(shí)間內(nèi)的健身和健康數(shù)據(jù),除了意識(shí)到的好處之外,我們看到圖標(biāo)中的向上走的曲線,就能給我們極大的鼓勵(lì),激勵(lì)我們繼續(xù)運(yùn)動(dòng)。

正如我們能想象到的,在管理健康信息時(shí),數(shù)據(jù)安全成為非常重要的因素。HealthKit 對(duì)于所有的 HealthKit 信息有絕對(duì)的控制權(quán),會(huì)直接傳遞到用戶(hù)手中。用戶(hù)可以準(zhǔn)許或者拒絕任何 App 獲取他們的健康數(shù)據(jù)的請(qǐng)求。

對(duì)于開(kāi)發(fā)者來(lái)說(shuō),我們需要請(qǐng)求許可方能讀取或者寫(xiě)入 HealthKit 數(shù)據(jù)。實(shí)際上,我們需要特別聲明一下,我們想影響獲取具體哪些數(shù)據(jù)。另外,任何使用 HealthKit 的 App 必須要包含一份 Privacy Policy(隱私協(xié)議),這樣用戶(hù)在進(jìn)行信息交易時(shí)會(huì)覺(jué)得更舒服一些。

關(guān)于 OneHourWalker 走路一小時(shí)

今天,我們要?jiǎng)?chuàng)建一個(gè)非常有趣的 App,既能讀取 HealthKit 中的信息,也能寫(xiě)入新的數(shù)據(jù)??匆幌?OneHourWalker 的外表吧:

OneHourWalker 是健身 App,能夠跟蹤用戶(hù)在一個(gè)小時(shí)內(nèi)走路或跑步的距離。用戶(hù)可以分享距離到 HealthKit上,就會(huì)被其他的健康 App 收集到數(shù)據(jù)。我知道,一小時(shí)聽(tīng)起來(lái)不太積極,至少對(duì)我而言是這樣。最后,用戶(hù)可以提早結(jié)束健身,仍然可以分享距離。

所以,聽(tīng)起來(lái)只需要把數(shù)據(jù)寫(xiě)入 HealthKit 即可。不過(guò)我們要讀取的數(shù)據(jù)是什么?

好問(wèn)題!當(dāng)我走路時(shí),我有可能是在羊腸小道上步行,也可能在森林中漫步。我常常穿越一些 low-hanging branches 區(qū)域。Being as I am 6’4”,這會(huì)帶來(lái)一些問(wèn)題。我們的解決方案是:我們會(huì)從 HealthKit 中讀取用戶(hù)的高度,然后顯示到 Label 控件上。這樣會(huì)比較友好地提示用戶(hù)的限制,這樣他或她就能避免在走路時(shí)糾纏在一起。

為了方便開(kāi)始,這里有一個(gè) OneHourWalker 的初始工程,下載然后運(yùn)行,看起來(lái)好像 App 可以運(yùn)行。計(jì)時(shí)器和定位系統(tǒng)都已經(jīng)在運(yùn)行了,所以我們只需要將注意力放在實(shí)施 HealthKit上,注意一下,六十分鐘后,計(jì)時(shí)器和定位系統(tǒng)就會(huì)自動(dòng)停止。

開(kāi)啟 HealthKit

第一步就是在應(yīng)用中開(kāi)啟 HealthKit 功能,在 Project Navigator 中,選中 OneHourWalker,然后點(diǎn)擊 Targets 下方的 OneHourWalker。接著,在屏幕上方的 tab 欄中點(diǎn)擊 Capabilities。

找到 Capabilities 清單底下,把 HealthKit 調(diào)到 On 狀態(tài)。這個(gè)動(dòng)作表示:把 HealthKit 資格添加到 App ID 中,把 HealthKit key 添加到 info plist 文件里,把 HealthKit 資格添加到資格文件中,以及連接 HealthKit.framework。就是這么簡(jiǎn)單。

開(kāi)始寫(xiě)代碼吧

找到 TimerViewController.swift,開(kāi)始把 HealthKit 引入 OneHourWalker。首先,讓我們創(chuàng)建一個(gè) HealthKitManager 實(shí)例。

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()

HealthKitManager.swift 里包含了所有有關(guān) HealthKit 的操作。里面有一些重要的方法,我們馬上就要開(kāi)始在這個(gè)文件里進(jìn)行編程了。

正如我們?cè)陂_(kāi)頭介紹的那樣,我們需要獲取用戶(hù)的許可來(lái)讀取和寫(xiě)入他們的健康數(shù)據(jù)。在 ViewDidLoad(),開(kāi)始獲取許可吧:

    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() 方法會(huì)調(diào)用 manager 的 authorizeHealthKit() 方法。如果一切順利,我們可以調(diào)用 setHeight() 方法,不過(guò)很快我們就會(huì)需要更多方法了。

    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 文件里,我們創(chuàng)建 authorizeHealthKit() 方法。除此之外,我們還需要?jiǎng)?chuàng)建 HealthKit store,將 App 連接到 HealthKit 數(shù)據(jù)。

    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)
            }
        }
    }


當(dāng)我們請(qǐng)求授權(quán)獲取用戶(hù)健康數(shù)據(jù)時(shí),我們需要特別表明我們只是想讀取和寫(xiě)入數(shù)據(jù)。對(duì)于我們來(lái)說(shuō),我們想讀取用戶(hù)的高度,所以他們可以避免容易實(shí)現(xiàn)誤導(dǎo)動(dòng)作。希望 HealthKit 能夠提供一個(gè) HKObject 參數(shù),可以獲取一個(gè)可理解的高度數(shù)據(jù)。我們也需要請(qǐng)求許可方能寫(xiě)入 HKObject 參數(shù),獲取用戶(hù)的走路或者跑步距離。

在處理了 OneHourWalker 的所有可能性后,我們?cè)谝粋€(gè) iPad 真機(jī)上測(cè)試了一下,制作正式的的請(qǐng)求。

HealthKitManager.swift 文件中創(chuàng)建 getHeight() 方法,能夠讀取用戶(hù)的高度數(shù)據(jù)。

    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)
    }
    

我們第一步就是查詢(xún)高度數(shù)據(jù)來(lái)創(chuàng)建一個(gè)猜測(cè)的時(shí)間參數(shù),我們獲取一段時(shí)間內(nèi)的所有高度信息,從過(guò)去到現(xiàn)在,當(dāng)然了,這就會(huì)返回給我們一個(gè)數(shù)組。我們只想要最近的高度,所以我們會(huì)讓數(shù)據(jù)中最新的數(shù)據(jù)排在最前面。

在創(chuàng)建查詢(xún)的過(guò)程中,我們會(huì)限制數(shù)組的總數(shù)為一。把可能出錯(cuò)的情況計(jì)算在內(nèi),我們把第一個(gè)也是唯一一個(gè) item 作為 lastHeight 的結(jié)果。接著,我們搞定 getHeight() 方法。最后,執(zhí)行查詢(xún)對(duì)應(yīng)用戶(hù)的健康數(shù)據(jù)。

回到 TimerViewController.swift,在 App 出現(xiàn)在用戶(hù)之前,我們假設(shè)已經(jīng)獲取了用戶(hù)的準(zhǔn)許,setHeight()getHealthKitPermission() 被調(diào)用。

var height: HKQuantitySample?

首先,我們需要聲明一個(gè)高度變量作為 HKQuantitySample 的實(shí)例。

    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() 方法,我們會(huì)創(chuàng)建我們的 setHeigth() 方法。高度數(shù)據(jù)例子表明我們請(qǐng)求之后返回的一個(gè) HKQuantity,它的 identifier 也就是 HKQuantityTypeIdentifierHeight。

下一步,我們調(diào)用 getHeight() 方法,也就是我們?cè)?manager 中創(chuàng)建的。有了高度,我們需要將它轉(zhuǎn)換成合適的字符串,展示到我們的 Label 控件中。照例,我們要考慮所有可能的錯(cuò)誤。

就這點(diǎn)而言,用戶(hù)能夠打開(kāi) App,看一下他們的高度,假設(shè)這里有記錄高度的 App,開(kāi)始計(jì)時(shí)器,然后追蹤跑步或者走路的距離。下一步就是處理寫(xiě)入數(shù)據(jù),所以用戶(hù)可以記錄所有的健身數(shù)據(jù)。

60分鐘后或更短時(shí)間內(nèi)用戶(hù)完成運(yùn)動(dòng),他/她會(huì)點(diǎn)擊 Share 按鈕將他們的距離發(fā)送到 Health 應(yīng)用里。所以,在 share() 方法中,讓我們調(diào)用 HealthKitManager.swift 里的 saveDistance() 方法,這樣,數(shù)據(jù)和日期都能被歸檔,用戶(hù)可以明天試著去挑戰(zhàn)他/她自己的記錄!

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

回到 manager,我們創(chuàng)建 saveDistance() 方法,首先,我們需要讓 HealthKit 知道我們想寫(xiě)入跑步距離和走路步數(shù),接著,我們將計(jì)量單位換成英里,分配正式的數(shù)量。HealthKit 的 saveObject() 方法將會(huì)寫(xiě)入用戶(hù)的健康數(shù)據(jù)。

    
    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!")
            }
        })
    }

回到 Health 應(yīng)用里,記錄的數(shù)據(jù)會(huì)包含在 Walking + Running Distance 里。當(dāng)然,我們也能看到一個(gè)具體的例子:Health Data tab > Fitness > Walking + Running Distance > Show All Data。我們的數(shù)據(jù)就在這清單里。點(diǎn)擊一行,然后就會(huì)看到我們的圖標(biāo)(目前還空著)。再次點(diǎn)擊這一行,就會(huì)出現(xiàn)所有的詳細(xì)信息。

有了 OneHourWalker,我們已經(jīng)成功地為全世界的 iOS 用戶(hù)的健康貢獻(xiàn)了我們的力量。然而,這僅僅是一個(gè)開(kāi)始。仍然有更多利用 HealthKit 讀取和寫(xiě)入健康數(shù)據(jù)的事情需要我們來(lái)做。

當(dāng)然,能夠獲取用戶(hù)的所有跟蹤信息也是非常棒的,人們可以非常容易的進(jìn)行每天和每天的比較,周和周的對(duì)比,以及其他朝著目標(biāo)推進(jìn)的對(duì)比。真正偉大之處在于,開(kāi)發(fā)者能夠提供全新的、有創(chuàng)造力的、有趣的方式來(lái)捕獲這些數(shù)據(jù)。

同樣的,HealthKit 應(yīng)用是最有趣的測(cè)試了!

這里是我們最終版本的 OneHourWalker

本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 作者:AppCoda,原文鏈接,原文日期:2016-03-22譯者:Crystal Sun;校對(duì):numbbbbb...
    梁杰_numbbbbb閱讀 982評(píng)論 0 6
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,881評(píng)論 25 709
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • 簡(jiǎn)介 App 正在改變世界,豐富人們的生活,并為像您一樣的開(kāi)發(fā)者提供前所未有的創(chuàng)新機(jī)會(huì)。因此,App Store ...
    o0_0o閱讀 3,718評(píng)論 2 48
  • 一場(chǎng)雷陣雨,家里也是小暴雨。不要問(wèn)我家里怎么回事,不想多談。老天爺生的我就是該受這些罪,我有什么辦法。 今天,和家...
    Gump灬咼閱讀 256評(píng)論 0 0

友情鏈接更多精彩內(nèi)容