LoginSignup
33
34

More than 5 years have passed since last update.

AppleWatchで心拍数を測定するアプリ

Last updated at Posted at 2016-09-03

概要

AppleWatchで心拍数を測定するアプリをつくる
IMG_3120.PNG

注意

どうやら現時点で、直接心拍計からデータを取得することは出来ないっぽい

あくまで心拍数を計測するのはappleの公式アプリケーションで、そこからデータを取得するっていう流れになる

図1.png

大まかな流れ

  1. HealthKitへのアクセス許可の取得
  2. HealthKitへ心拍計測クエリを実行
  3. 返却値を画面に表示

準備

CapabilitiesにHealthKitを追加
HeartRateTest_xcodeproj_と_AppDelegate_swift.png

WatchKitExtenssionのCapabilitiesにもHealthKitを追加
HeartRateTest_xcodeproj_と_AppDelegate_swift-2.png

アクセス許可の取得

HealthKitで用意されているクラスHKHealthStoreの関数を利用して認証を行う
認証の際にアプリで利用するデータの種類を指定する必要がある
今回は心拍数を利用したいのでHKQuantityTypeIdentifierHeartRateを指定

InterfaceController.swift

// HealthKitで扱うデータを管理するクラス(データの読み書きにはユーザの許可が必要)
let healthStore = HKHealthStore()

override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()

        // HealthKitがデバイス上で利用できるか確認
        guard HKHealthStore.isHealthDataAvailable() else {
            self.label.setText("not available")
            return
        }

        // アクセス許可をユーザに求める
        let dataTypes = Set([HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!])
        self.healthStore.requestAuthorizationToShareTypes(nil, readTypes: dataTypes) { (success, error) -> Void in
            guard success else {
                self.label.setText("not allowed")
                return
            }
        }
}

さらにiOS側のAppDelegate.swiftに以下を追加

AppDelegate.swift
import UIKit
import HealthKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let healthStore = HKHealthStore()

    func applicationShouldRequestHealthAuthorization(application: UIApplication) {
        guard HKHealthStore.isHealthDataAvailable() else {
            return
        }

        let dataTypes = Set([HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!])
        healthStore.requestAuthorizationToShareTypes(nil, readTypes: dataTypes, completion: { (result, error) -> Void in
        })
    }
}

実行するとAppleWatch上でHealthKitへのアクセス許可のダイアログが表示され、iOS上で許可するかどうかの画面が表示される

HealthKitへ心拍測定のクエリを実行、画面表示

ボタンを押したら心拍数の測定を始めるように実装
ボタンとラベルを適当に配置して完成

InterfaceController.swift
    // 取得したデータの単位、今回はBPM
    let heartRateUnit = HKUnit(fromString: "count/min")
    // HealthStoreへのクエリ
    var heartRateQuery: HKQuery?

    @IBAction func buttonTapped() {
        if self.heartRateQuery == nil {
            // start
            // クエリ生成
            self.heartRateQuery = self.createStreamingQuery()
            // クエリ実行
            self.healthStore.executeQuery(self.heartRateQuery!)
            self.button.setTitle("Stop")
            self.messageLabel.setText("Measuring...")
        }
        else {
            // end
            self.healthStore.stopQuery(self.heartRateQuery!)
            self.heartRateQuery = nil
            self.button.setTitle("Start")
            self.messageLabel.setText("")
        }
    }

    // healthStoreへのクエリ生成
    private func createStreamingQuery() -> HKQuery {
        let predicate = HKQuery.predicateForSamplesWithStartDate(NSDate(), endDate: nil, options: .None)

        // HKAnchoredObjectQueryだと他のアプリケーションによる更新を検知することができる
        let query = HKAnchoredObjectQuery(type: heartRateType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, samples, deletedObjects, anchor, error) -> Void in
            self.addSamples(samples)
        }
        // Handler登録、上でやってるからいらないかも...
        query.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
            self.addSamples(samples)
        }

        return query
    }

    // 取得したデータをLabelに表示
    private func addSamples(samples: [HKSample]?) {
        guard let samples = samples as? [HKQuantitySample] else {
            return
        }
        guard let quantity = samples.last?.quantity else {
            return
        }
        label.setText("\(quantity.doubleValueForUnit(heartRateUnit))")
    }

ソースコード

InterfaceController.swift

import WatchKit
import Foundation
import HealthKit

class InterfaceController: WKInterfaceController {

    @IBOutlet var label: WKInterfaceLabel!
    @IBOutlet var messageLabel: WKInterfaceLabel!
    @IBOutlet var button: WKInterfaceButton!

    // HealthKitで扱うデータを管理するクラス(データの読み書きにはユーザの許可が必要)
    let healthStore = HKHealthStore()
    // 取得したいデータの識別子、今回は心拍数
    let heartRateType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!
    // 取得したデータの単位、今回はBPM
    let heartRateUnit = HKUnit(fromString: "count/min")
    // HealthStoreへのクエリ
    var heartRateQuery: HKQuery?

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()

        // HealthKitがデバイス上で利用できるか確認
        guard HKHealthStore.isHealthDataAvailable() else {
            self.label.setText("not available")
            return
        }

        // アクセス許可をユーザに求める
        let dataTypes = Set([self.heartRateType])
        self.healthStore.requestAuthorizationToShareTypes(nil, readTypes: dataTypes) { (success, error) -> Void in
            guard success else {
                self.label.setText("not allowed")
                return
            }
        }
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

    @IBAction func buttonTapped() {
        if self.heartRateQuery == nil {
            // start
            // クエリ生成
            self.heartRateQuery = self.createStreamingQuery()
            // クエリ実行
            self.healthStore.executeQuery(self.heartRateQuery!)
            self.button.setTitle("Stop")
            self.messageLabel.setText("Measuring...")
        }
        else {
            // end
            self.healthStore.stopQuery(self.heartRateQuery!)
            self.heartRateQuery = nil
            self.button.setTitle("Start")
            self.messageLabel.setText("")
        }
    }

    // healthStoreへのクエリ生成
    private func createStreamingQuery() -> HKQuery {
        let predicate = HKQuery.predicateForSamplesWithStartDate(NSDate(), endDate: nil, options: .None)

        // HKAnchoredObjectQueryだと他のアプリケーションによる更新を検知することができる
        let query = HKAnchoredObjectQuery(type: heartRateType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, samples, deletedObjects, anchor, error) -> Void in
            self.addSamples(samples)
        }
        // Handler登録、上でやってるからいらないかも...
        query.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
            self.addSamples(samples)
        }

        return query
    }

    // 取得したデータをLabelに表示
    private func addSamples(samples: [HKSample]?) {
        guard let samples = samples as? [HKQuantitySample] else {
            return
        }
        guard let quantity = samples.last?.quantity else {
            return
        }
        label.setText("\(quantity.doubleValueForUnit(heartRateUnit))")
    }
}
AppDelegate.swift
import UIKit
import HealthKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let healthStore = HKHealthStore()

    func applicationShouldRequestHealthAuthorization(application: UIApplication) {
        guard HKHealthStore.isHealthDataAvailable() else {
            return
        }

        let dataTypes = Set([HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!])
        healthStore.requestAuthorizationToShareTypes(nil, readTypes: dataTypes, completion: { (result, error) -> Void in
        })
    }

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}


参考

watchOS-2-Sampler
HealthKit Framework Reference
[iOS 8] HealthKitを実装する(1) HealthKit簡易リファレンス
Healthkitから脈拍を取得するapple watchアプリ

33
34
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
34