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

【編者按】本文作者為 Matthew Maher,文章手把手地介紹了如何借助 HealthKit 建立簡單的健身應用,包含諸多代碼實例。本文系國內 ITOM 管理平臺 OneAPM 編譯呈現。

根據新聞報導,健康與健美在今時今日的重要程度比已往任何時候都高。說起來有點可笑,似乎就在幾天之前,筆者就見到過類似的新聞。或許,這是當人逐漸變老之后揮之不去的感覺吧——渴望保持健康以及健美的感覺。不管怎么說,健康與健美是一個重要話題。技術的進步,尤其是移動應用與硬件世界的不斷提高,正為這個似乎日益成長的話題帶來全新的契機。

HealthKit 是蘋果公司推出的一款移動應用平臺,旨在為重要、可追蹤的健康數據與注重健康、熱衷鍛煉的科技消費者搭起橋梁。這很酷。用戶可以輕松地追蹤一段時間內可測量的健身與健康數據。除了了解自身的健康數據,看到圖表中喜人的增長曲線也的確鼓舞人心。

正如人們想象的那樣,在管理健康信息時安全是非常重要的考慮因素。HealthKit 直截了當地將所有 HealthKit 信息的絕對控制權置于用戶的手中。用戶可以授權或拒絕任何應用對其健康數據發(fā)出的讀取請求。

作為開發(fā)者,我們需要征求許可才能從/向 HealthKit 讀取/寫入數據。實際上,我們需要明確地聲明打算讀取或改變的數據。此外,任何使用 HealthKit 的應用都必須包含隱私政策,這樣一來,用戶才能對其信息的處理感到更加放心。

關于 OneHourWalker

在本文中,我們將打造一個有趣的小應用,它會從 HealthKit 讀取數據,也會向其寫入新數據。來見一見 OneHourWalker 吧。

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

OneHourWalker 是一款追蹤使用者在一個小時內行走或跑步之距離的健身應用。用戶可以將距離與 HealthKit 分享,之后就能在健康應用中讀取之。我知道,一個小時聽起來有點過于樂觀了(至少筆者本人可能無法堅持下去)。因此,用戶也可以提早中止計數,并分享距離。

額,到目前為止,似乎 OneHourWalker 只會向 HealthKit 寫入數據。我們需要讀取什么數據呢?

好問題!在步行鍛煉時,我喜歡選擇鄉(xiāng)間或林間小路。常常,我會遇到樹枝低垂的區(qū)域。而我是一條身高 193cm 的漢子,這真的讓我很苦惱。解決辦法是:從 HealthKit 讀取用戶的身高數據,將之打印為應用的一個標簽。這個標簽可以作為對用戶的善意提醒,這樣,他們就能避免在步行時被樹枝打到。

首先,點此下載 OneHourWalker 的初始項目。先試著跑起來,找找應用運行的感覺。計數器與地點追蹤功能已經在運行了,所以我們只需專注于 HealthKit 實現。注意,當到達 60 分鐘時間點時,計算器與追蹤都會停止。

啟用 HealthKit

首先,在我們的應用中啟用 HealthKit。在項目導航中,點擊 OneHourWalker,之后點擊 Targets 下面的 OneHourWalker,之后選擇屏幕頂部的 Capabilities 選項。

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

查看 Capabilities 列表的底部,啟用 HealthKit。這一簡單的操作會將 HealthKit 權限添加到 App ID,將 HealthKit 鍵添加到 info plist 文件,將 HealthKit 權限添加到授權文件,并且與 HealthKit.framework 相連接。就是這么簡單。

開始編程

接下來,跳轉到 TimerViewController.swift,開始將 HealthKit 引入 OneHourWalker。首先,創(chuàng)建一個 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 中,我們會創(chuàng)建 authorizeHealthKit() 方法。然而,除此之外,我們需要創(chuàng)建 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 中,創(chuàng)建從 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)
}

查詢身高數據的第一步是創(chuàng)建一個斷言以定義時間參數。我們是在請求一段時間內的所有身高數據——與當前日期相距甚遠的一個過去的日期。顯然,這會返回一個數組。然而,我們只想要最近期的身高,因此,我們請求數據時可以讓最新的數據排在數組的最前頭。

在構建這一查詢時,我們會把數組的長度限制為1。在考慮好出現錯誤的可能性后,我們會將結果中的首個也即唯一一個數組項目分配給 lastHeight。接下來,完善 getHeight() 方法。最后,針對用戶的健康數據執(zhí)行查詢。

回到 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() 方法之上,我們會創(chuàng)建 setHeight() 方法。我們請求的身高數據樣本以 HKQuantity 返回,標識符 HKQuantityTypeIdentifierHeight 知道這一對象。

接下來,調用在 manager 中創(chuàng)建的 getHeight() 方法。有了身高樣本,我們還需要將之翻譯為恰當的字符串以展示在標簽中。與往常一樣,考慮所有可能的錯誤情況是很重要的。

到此,用戶就可以打開 app,查看他們的身高(如果他的健康應用中記錄著身高數據),開啟計時器,追蹤他跑步或行走的距離了。接下來,我們要處理將距離數據寫入健康應用的過程,這樣,用戶才能在同一個應用中保存其所有的健身數據。

在用戶結束外出鍛煉之后,不管有沒有到60分鐘,他可能會使用 Share(分享)按鈕將其辛苦賺得的運動距離發(fā)送到健康應用。所以,在 share() 方法中,我們需要調用 HealthKitManager.swiftsaveDistance() 方法來實現這一過程。在這個方法中,我們會發(fā)送運動距離以及取得該距離的日期。這樣,用戶便能在第二天爭取更好的成績。

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

接下來,回到 manager,我們要在此處創(chuàng)建 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(顯示所有數據)。我們的數據就在此列表中。輕擊一個單元,我們的圖標(目前還未設置)就會與距離一同出現。再次點擊此單元,就能看到完整的細節(jié)數據。

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

借助 OneHourWalker,我們便能為全世界 iOS 用戶的身體健康貢獻一份力量。然而,這只是一個開始。在使用 HealthKit 讀取并修改健康數據的道路上,還有非常多的可能性。

當然,對用戶而言,擁有這些可追蹤數據的好處很多。人們可以輕松地按照日期、星期進行比較,從而激勵自己朝著目標努力。不過,真正的偉大之處在于,開發(fā)者可以提供全新的,富有創(chuàng)造力的有趣方法來獲取數據。

歡迎大家對 HealthKit 應用進行測試。點擊此處查看 OneHourWalker 的最終版本。

本文系 OneAPM 工程師編譯整理。OneAPM Mobile Insight真實用戶體驗為度量標準進行 Crash 分析,監(jiān)控網絡請求及網絡錯誤,提升用戶留存。訪問 OneAPM 官方網站感受更多應用性能優(yōu)化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術博客

本文轉自 OneAPM 官方博客

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

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

推薦閱讀更多精彩內容