用 HealthKit 來開發一個健身 App

作者:AppCoda,原文鏈接,原文日期:2016-03-22
譯者:Crystal Sun;校對:numbbbbb;定稿:Cee

看新聞我們也知道,比起歷史上任何一個時刻,健身和健康在今天都更加重要。說起來也挺好笑的,我似乎記得幾天前新聞也在說同樣的事情,也許是因為年紀越來越大的緣故,我更需要健康和健身。不管怎么說,這是一個熱門話題。隨著技術的不斷進步,手機應用和硬件在世界范圍內都變得流行起來,這些都給日益流行的健身健康話題加入了新的元素。

HealthKit 是蘋果公司的重要橋梁,把追蹤的重要的健康數據同有健康意識的科技消費者、運動迷、平常使用 iPhone 的人連接了起來。這很酷,用戶可以很容易的就追蹤衡量一段時間內的健身和健康數據,除了意識到的好處之外,我們看到圖標中向上走的曲線,就能給我們極大的鼓勵,激勵我們繼續運動。

正如我們能想象到的,在管理健康信息時,數據安全成為非常重要的因素。HealthKit 對于所有的 HealthKit 信息有絕對的控制權,會直接傳遞到用戶手中。用戶可以準許或者拒絕任何 App 獲取他們的健康數據的請求。

對于開發者來說,我們需要請求許可方能讀取或者寫入 HealthKit 數據。實際上,我們需要特別聲明一下,我們想影響獲取具體哪些數據。另外,任何使用 HealthKit 的 App 必須要包含一份 Privacy Policy(隱私協議),這樣用戶在進行信息交易時會覺得更舒服一些。

關于走路一小時(OneHourWalker)

今天,我們要創建一個非常有趣的 App,既能讀取 HealthKit 中的信息,也能寫入新的數據。看一下 OneHourWalker 的外表吧:

OneHourWalker 是一個健身 App,能夠跟蹤用戶在一個小時內走路或跑步的距離。用戶可以把距離分享到 HealthKit,這樣就能在健康應用中查看。我知道,整整一個小時聽起來確實有點嚇人,至少對我而言是這樣。因此,用戶可以提前結束健身,此時仍然可以分享距離。

所以,聽起來只需要把數據寫入 HealthKit 即可。不過我們要讀取的數據是什么?

好問題!我喜歡在樹林里的小路上漫步。我常常穿越一些枝杈縱橫的區域。因為我是八尺大漢,這會帶來一些問題。我們的解決方案是:我們會從 HealthKit 中讀取用戶的身高,然后顯示到 Label 控件上。這樣會比較友好地提示用戶,幫他避免不適合運動的區域。

下面是 OneHourWalker 的初始工程,下載然后運行,看起來好像 App 可以運行。計時器和定位系統都已經在運行了,所以我們只需要將注意力放在使用 HealthKit 上,注意一下,六十分鐘后,計時器和定位系統就會自動停止。

啟用 HealthKit

第一步就是在應用中開啟 HealthKit 功能,在 Project Navigator 中,選中 OneHourWalker,然后點擊 Targets 下方的 OneHourWalker。接著,在屏幕上方的 tab 欄中點擊 Capabilities。

在 Capabilities 底部把 HealthKit 設置為 On。這會把 HealthKit entitlement 添加到 App ID 中、把 HealthKit key 添加到 info plist 文件中、把 HealthKit entitlement 添加到資格文件中、連接 HealthKit.framework。就是這么簡單。

開始寫代碼吧

找到 TimerViewController.swift,下面我們給 OneHourWalker 添加 HealthKit。首先我們創建一個 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()

HealthKitManager.swift 里包含了所有和 HealthKit 有關的操作。里面有一些重要的方法,稍后我們會實現它。

正如開頭介紹的那樣,我們需要獲取用戶的授權,從而讀取和寫入他們的健康數據。在 ViewDidLoad()中獲取授權:

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.requestWhenInUseAuthorization();
        
        if CLLocationManager.locationServicesEnabled(){
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
        }
        else {
            print("Need to Enable Location");
        }
        
        // 不向用戶請求許可就無法獲取用戶的 HealthKit 數據
        getHealthKitPermission()
    }

getHealthKitPermission() 方法會調用 manager 的 authorizeHealthKit() 方法。如果一切順利,我們可以調用 setHeight() 方法,稍后我們會介紹這個方法。

    func getHealthKitPermission() {
        // 在 HealthKitManager.swift 文件里尋找授權情況。
        healthManager.authorizeHealthKit { (authorized,  error) -> Void in
            if authorized {
                
                // 獲得然后設置用戶的高度
                self.setHeight()
            } else {
                if error != nil {
                    print(error)
                }
                print("Permission denied.")
            }
        }
    }

HealthKitManager.swift 文件中創建 authorizeHealthKit() 方法。除此之外,我們還需要創建 HealthKit store,將 App 連接到 HealthKit 數據。

    let healthKitStore: HKHealthStore = HKHealthStore()
    
    func authorizeHealthKit(completion: ((success: Bool, error: NSError!) -> Void)!) {
        
        // 聲明我們想從 HealthKit 里讀取的健康數據的類型
        let healthDataToRead = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)!)
        
        // 聲明我們想寫入 HealthKit 的數據的類型
        let healthDataToWrite = Set(arrayLiteral: HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)!)
        
        // 以防萬一 OneHourWalker 在 iPad 中打開
        if !HKHealthStore.isHealthDataAvailable() {
            print("Can't access HealthKit.")
        }
        
        // 請求可以讀取和寫入數據的權限
        healthKitStore.requestAuthorizationToShareTypes(healthDataToWrite, readTypes: healthDataToRead) { (success, error) -> Void in
            if( completion != nil ) {
                completion(success:success, error:error)
            }
        }
    }

當我們請求授權獲取用戶健康數據時,需要特別表明我們只是想讀取和寫入數據。對于這個應用來說,我們想讀取用戶的身高,從而幫助他們避免撞到樹枝。我們期望 HealthKit 提供一個 HKObject 實體,我們可以把它轉換成可讀性更高的身高值。此外,我們還需要申請寫入權限,從而把用戶步行和跑步的距離寫入 HKObject 實體。

我們會在處理完 iPad 屏幕適配之后發起權限請求。

我們在 HealthKitManager.swift 文件中創建 getHeight() 方法,從 HealthKit 中讀取用戶的高度數據。

    func getHeight(sampleType: HKSampleType , completion: ((HKSample!, NSError!) -> Void)!) {
        
        // 創建斷言,以查詢高度
        let distantPastHeight = NSDate.distantPast() as NSDate
        let currentDate = NSDate()
        let lastHeightPredicate = HKQuery.predicateForSamplesWithStartDate(distantPastHeight, endDate: currentDate, options: .None)
        
        // 獲得最近的高度值
        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)

        // 從 HealthKit 里獲取最近的高度值
        let heightQuery = HKSampleQuery(sampleType: sampleType, predicate: lastHeightPredicate, limit: 1, sortDescriptors: [sortDescriptor]) { (sampleQuery, results, error ) -> Void in
                
                if let queryError = error {
                    completion(nil, queryError)
                    return
                }

                // 把第一個 HKQuantitySample 作為最近的高度值
                let lastHeight = results!.first
            
                if completion != nil {
                    completion(lastHeight, nil)
                }
        }
        
        // 是時候執行查詢了
        self.healthKitStore.executeQuery(heightQuery)
    }

查詢身高數據的第一步是創建一個斷言,用它定義時間參數。我們會獲取一段時間內的所有身高信息。當然,這會返回一個數組。我們只想要最近的身高,所以我們對數據排序,讓數據中最新的數據排在最前面。

在創建查詢的過程中,我們把數組的長度限制為一。處理完可能出現的錯誤之后,我們把第一個也是唯一一個 item 作為 lastHeight 的結果。接著,調用 getHeight() 的回調函數。最后,執行我們的查詢操作。

回到 TimerViewController.swift,在用戶授權完成之后,用戶開始使用 App 之前,需要在 getHealthKitPermission() 中調用 setHeight()

var height: HKQuantitySample?

首先,我們需要給 HKQuantitySample 實例聲明一個高度變量。

    func setHeight() {

        // 創建高度 HKSample。
        let heightSample = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
        
        // 調用 HealthKitManager 的 getSample() 方法,來獲取用戶的高度。
        self.healthManager.getHeight(heightSample!, completion: { (userHeight, error) -> Void in
            
            if( error != nil ) {
                print("Error: \(error.localizedDescription)")
                return
            }
            
            var heightString = ""
            
            self.height = userHeight as? HKQuantitySample
            
            // 把高度轉換成用戶本地的計量單位。
            if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) {
                let formatHeight = NSLengthFormatter()
                formatHeight.forPersonHeightUse = true
                heightString = formatHeight.stringFromMeters(meters)
            }
            
            // 設置 label 顯示用戶的高度。
            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                self.heightLabel.text = heightString
            })
        })
        
    }

share() 方法之前創建 setHeigth() 方法。我們請求的身高數據會返回一個 HKQuantity,它的 identifier 是 HKQuantityTypeIdentifierHeight

接著,我們調用 manager 中的 getHeight() 方法。有了身高數據,我們需要將它轉換成合適的字符串,展示到我們的 Label 控件中。照例,我們要考慮所有可能的錯誤。

現在,用戶能夠打開 App,查看他們的身高,將身高記錄到健康應用中,開始計時,然后追蹤跑步或者走路的距離。下一步就是處理寫入數據,讓用戶可以記錄所有的健身數據。

用戶完成運動之后(無論是整整一小時還是不到一小時),他/她會點擊 Share 按鈕,將他們的距離發送給 Health 應用。所以我們在 share() 方法中調用 HealthKitManager.swift 里的 saveDistance() 方法,這樣數據和日期都能被歸檔,明天用戶可以試著去挑戰他/她自己的記錄!

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

回到 manager,我們創建 saveDistance() 方法,首先,我們需要讓 HealthKit 知道我們想寫入跑步距離和走路步數,接著,我們將計量單位換成英里并賦值給實體。HealthKit 的 saveObject() 方法將會寫入用戶的健康數據。

    func saveDistance(distanceRecorded: Double, date: NSDate ) {
                
        // 設置跑步距離或走路步數的數量的類型
        let distanceType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
        
        // 把計量單位設置成英里
        let distanceQuantity = HKQuantity(unit: HKUnit.mileUnit(), doubleValue: distanceRecorded)

        // 設置正式的 Quantity Sample。
        let distance = HKQuantitySample(type: distanceType!, quantity: distanceQuantity, startDate: date, endDate: date)
        
        // 保存距離數量,把健康數據寫入 HealthKit
        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

本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 http://swift.gg

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容

  • 原文鏈接作者:AppCoda原文日期:2016-03-22 看新聞我們也知道,比起歷史上任何一個時刻,健身和健康在...
    sing_crystal閱讀 2,421評論 1 12
  • 【編者按】本文作者為 Matthew Maher,文章手把手地介紹了如何借助 HealthKit 建立簡單的健身應...
    OneAPM_Official閱讀 660評論 1 1
  • 蔡瀾說,食物的甜酸苦辣,和人生一樣,有哀愁也有它的歡樂。 分享食材,感受日常生活的美好。 蔥 在菜市場買了一斤芹菜...
    Echohou閱讀 887評論 27 2
  • 作為一個南方人畢業后稀里糊涂的來北京工作,如今五年過去了,一想到自己仍舊一無所有,難免有些悲傷和迷茫。
    真笑臉男閱讀 243評論 0 0
  • L是我剛畢業工作時的一個同事,業務能力強,平時為人大方慷慨,人緣挺好,不過他有一個缺點——喜歡抱怨,習慣推卸責任,...
    amazing2017閱讀 269評論 1 1