如何在 iOS 中从 Health Kit 实时获取附近的心率数据?

How to get Heart Rate Data near by Real Time from Health Kit in iOS?

我在 Watch 中创建了一个会话,并在 Health Kit 中更新了心率数据。现在,我想在 iPhone 屏幕上显示当前心率。手表传感器更新 Health kit 中的心率数据,但 iPhone 应用程序无法从 Health kit 中获取实时数据。我已经测试了以下两种情况。我还记得这个 method/function 使用计时器但它没有获取实时数据。

注意:当我打开Health App并重新打开我的应用程序时,它会自动刷新数据。如果我的应用程序一直在前台运行,那么下面的代码不会刷新健康工具包中的最新数据

1.尝试使用 HKSampleQuery

获取实时心率数据
        let calendar = NSCalendar.current
        let components = calendar.dateComponents([.year, .month, .day], from: Date())
        let startDate : NSDate = calendar.date(from: components)! as NSDate
        let endDate : Date = calendar.date(byAdding: Calendar.Component.day, value: 1, to: startDate as Date)!

        let predicate = HKQuery.predicateForSamples(withStart: startDate as Date, end: endDate, options:[])

        //descriptor
        let sortDescriptors = [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)]

        self.heartRateQuery = HKSampleQuery(sampleType: self.heartRateType, predicate: predicate, limit: 1, sortDescriptors: sortDescriptors, resultsHandler: { (query:HKSampleQuery,  results:[HKSample]?, error:Error?) in

            guard error == nil else { print("error in getting data"); return }

            self.collectCurrentHeartRateSample(currentSampleTyple: results)

        })

        self.healthStore.execute(self.heartRateQuery!)

2。尝试使用 HKAnchoredObjectQuery

获取实时心率数据
    let sampleType : HKSampleType =  HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
    let predicate : NSPredicate =  HKQuery.predicateForSamples(withStart: startDate as Date, end: endDate, options: [])
    let anchor: HKQueryAnchor = HKQueryAnchor(fromValue: 0)

    let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: anchor, limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, anchor, error ) in

        self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)

    }

    anchoredQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
        self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)
    }

    self.healthStore.execute(anchoredQuery)

=============================================

解析数据

func collectCurrentHeartRateSample(currentSampleTyple : [HKSample]?, deleted : 

    [HKDeletedObject]?){

    //    func collectCurrentHeartRateSample(currentSampleTyple : [HKSample]?){

            DispatchQueue.main.async {

                self.currentHeartRateSample = currentSampleTyple

                //Get Last Sample of Heart Rate
                self.currentHeartLastSample = self.currentHeartRateSample?.last
                print("lastSample : \(String(describing: self.currentHeartLastSample))")

                if self.currentHeartLastSample != nil {

                    let result = self.currentHeartLastSample as! HKQuantitySample

                    let heartRateBPM = result.quantity.doubleValue(for: HKUnit(from: "count/min"))
                    let heartRateBPMUnit = "count/min"

                    let deviceUUID = self.currentHeartLastSample?.uuid
                    let deviceIdentity = result.sourceRevision.source.name
                    let deviceProductName = self.currentHeartLastSample?.device?.name
                    let deviceProductType = result.sourceRevision.productType
                    let deviceOSVersion = result.sourceRevision.version

                    let startDate = self.currentHeartLastSample?.startDate
                    let endDate = self.currentHeartLastSample?.endDate

                    self.aCollectionView.reloadData()
                }


            }

        }

我认为最好的方法是使用 Watch Communication 将心率数据简单地发送到 phone 应用程序。

在手表代码中:

func send(heartRate: Int) {
    guard WCSession.default.isReachable else {
        print("Phone is not reachable")
        return
    }

    WCSession.default.sendMessage(["Heart Rate" : heartRate], replyHandler: nil) { error in
        print("Error sending message to phone: \(error.localizedDescription)")
    }
 }

并在 phone 上您收到数据:

func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
    if let heartRate = message["Heart Rate"] {
        print("Received heart rate: \(heartRate)")
    } else {
        print("Did not receive heart rate =[")
    }
}

这应该几乎是实时发生的。或者,还有另一种不太可靠的解决方案 (imo),即每 5 秒左右执行一次心率查询,但如果我理解正确,你已经尝试过了,但它没有用。

这里是我自己对获取附近实时心率的分析。

1. 如果您使用 iPhone 应用程序访问 Health Kit 数据,在这种情况下,Health Kit DB 不会经常 updated/refreshed。因此,您的应用程序无法通过 iPhone app.

获取实时最新更新的数据

2. 使用手表应用程序,您可以通过 Health Kit DB 访问近乎实时的数据。 Watch app 能够获取实时最新更新的 Health Kit 数据。

3. 您需要将数据从手表传输到 iPhone 应用程序。这是供您参考的代码。您可以根据需要编写代码。您只需要通过 HKQuery

访问心率
let defaultSession = WCSession.default

let healthStore = HKHealthStore()

var currentHeartRateSample : [HKSample]?

var currentHeartLastSample : HKSample?

var currentHeartRateBPM = Double()


//Get Heart Rate from Health Kit

func getCurrentHeartRateData(){

    let calendar = Calendar.current
    let components = calendar.dateComponents([.year, .month, .day], from: Date())
    let startDate : Date = calendar.date(from: components)!
    let endDate : Date = calendar.date(byAdding: Calendar.Component.day, value: 1, to: startDate as Date)!

    let sampleType : HKSampleType =  HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
    let predicate : NSPredicate =  HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
    let anchor: HKQueryAnchor = HKQueryAnchor(fromValue: 0)

    let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: anchor, limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, anchor, error ) in

        if samples != nil {

            self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)

        }

    }

    anchoredQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
        self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)
    }

    self.healthStore.execute(anchoredQuery)

}


//Retrived necessary parameter from HK Sample
func collectCurrentHeartRateSample(currentSampleTyple : [HKSample]?, deleted : [HKDeletedObject]?){

        self.currentHeartRateSample = currentSampleTyple

        //Get Last Sample of Heart Rate
        self.currentHeartLastSample = self.currentHeartRateSample?.last

        if self.currentHeartLastSample != nil {

            let lastHeartRateSample = self.currentHeartLastSample as! HKQuantitySample

            self.currentHeartRateBPM = lastHeartRateSample.quantity.doubleValue(for: HKUnit(from: "count/min"))
            let heartRateStartDate = lastHeartRateSample.startDate
            let heartRateEndDate = lastHeartRateSample.endDate

            //Send Heart Rate Data Using Send Messge

            DispatchQueue.main.async {

                let message = [
                    "HeartRateBPM" : "\(self.currentHeartRateBPM)",
                    "HeartRateStartDate" : "\(heartRateStartDate)",
                    "HeartRateEndDate" : "\(heartRateEndDate)"
                ]

//Transfer data from watch to iPhone
                self.defaultSession.sendMessage(message, replyHandler:nil, errorHandler: { (error) in
                    print("Error in send message : \(error)")
                })

            }

        }


}