You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于HealthKit:计算单日平均心率及中等心率区间运动时长

HealthKit单日平均心率计算与中等心率区间时长统计方案

你的现有代码仅生成了模拟心率样本,未实现从HealthKit数据库查询真实数据、计算平均值及统计区间时长的核心逻辑。以下是完整实现方案:

一、前置准备:权限申请

首先必须请求读取心率数据的权限,否则无法访问HealthKit中的数据:

import HealthKit

class HealthKitManager {
    let healthStore = HKHealthStore()
    
    // 请求心率读取权限
    func requestHeartRateAuthorization(completion: @escaping (Bool, Error?) -> Void) {
        guard HKHealthStore.isHealthDataAvailable() else {
            completion(false, NSError(domain: "HealthKit", code: -1, userInfo: [NSLocalizedDescriptionKey: "HealthKit不可用"]))
            return
        }
        
        let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
        let readTypes: Set<HKObjectType> = [heartRateType]
        
        healthStore.requestAuthorization(toShare: nil, read: readTypes) { success, error in
            completion(success, error)
        }
    }
}

二、计算最大心率

中等心率区间基于最大心率的64%-76%,通常采用220 - 年龄的通用公式,也可读取用户在健康APP中设置的信息计算更精准值:

// 计算最大心率(示例:基于年龄)
func calculateMaxHeartRate(age: Int) -> Double {
    return 220 - Double(age)
}

// 从HealthKit获取用户年龄(可选)
func getUserAge(completion: @escaping (Int?) -> Void) {
    let birthdayType = HKObjectType.characteristicType(forIdentifier: .dateOfBirth)!
    do {
        let birthday = try healthStore.dateOfBirthComponents()
        let age = Calendar.current.dateComponents([.year], from: birthday.date!, to: Date()).year!
        completion(age)
    } catch {
        completion(nil)
    }
}

三、实现单日平均心率查询

使用HKStatisticsQuery直接计算指定日期内的心率平均值:

func getDailyAverageHeartRate(forDate date: Date, completion: @escaping (Double?, Error?) -> Void) {
    let cal = Calendar.current
    let startDate = cal.startOfDay(for: date)
    let endDate = cal.date(byAdding: .day, value: 1, to: startDate)!
    
    let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
    let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
    
    // 创建统计查询,计算平均值
    let query = HKStatisticsQuery(quantityType: heartRateType,
                                  quantitySamplePredicate: predicate,
                                  options: .average) { _, result, error in
        guard let result = result, let averageQuantity = result.averageQuantity() else {
            completion(nil, error)
            return
        }
        
        // 将心率转换为count/min单位
        let averageHeartRate = averageQuantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
        completion(averageHeartRate, nil)
    }
    
    healthStore.execute(query)
}

四、统计中等心率区间时长(含30/60分钟维度)

先获取当日所有心率样本,遍历判断连续处于区间内的时间段,同时统计总时长、单次持续≥30分钟的累计时长、单次持续≥60分钟的累计时长:

struct HeartRateIntervalStats {
    let totalMediumZoneDuration: TimeInterval // 中等区间总时长
    let durationWith30MinPlusSessions: TimeInterval // 单次≥30分钟的累计时长
    let durationWith60MinPlusSessions: TimeInterval // 单次≥60分钟的累计时长
}

func getMediumHeartRateZoneStats(forDate date: Date, maxHeartRate: Double, completion: @escaping (HeartRateIntervalStats?, Error?) -> Void) {
    let cal = Calendar.current
    let startDate = cal.startOfDay(for: date)
    let endDate = cal.date(byAdding: .day, value: 1, to: startDate)!
    
    let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
    let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
    
    // 获取所有心率样本
    let query = HKSampleQuery(sampleType: heartRateType,
                              predicate: predicate,
                              limit: HKObjectQueryNoLimit,
                              sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)]) { _, samples, error in
        guard let samples = samples as? [HKQuantitySample], error == nil else {
            completion(nil, error)
            return
        }
        
        let lowerBound = maxHeartRate * 0.64
        let upperBound = maxHeartRate * 0.76
        var totalDuration: TimeInterval = 0
        var currentSessionStart: Date?
        var thirtyMinPlusTotal: TimeInterval = 0
        var sixtyMinPlusTotal: TimeInterval = 0
        
        // 遍历样本,计算连续处于中等区间的时长
        for sample in samples {
            let heartRate = sample.quantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
            let isInMediumZone = heartRate >= lowerBound && heartRate <= upperBound
            let sampleDuration = sample.endDate.timeIntervalSince(sample.startDate)
            
            if isInMediumZone {
                if currentSessionStart == nil {
                    currentSessionStart = sample.startDate
                }
                totalDuration += sampleDuration
            } else {
                // 退出中等区间,计算当前会话时长
                if let sessionStart = currentSessionStart {
                    let sessionDuration = sample.startDate.timeIntervalSince(sessionStart)
                    if sessionDuration >= 30 * 60 {
                        thirtyMinPlusTotal += sessionDuration
                        if sessionDuration >= 60 * 60 {
                            sixtyMinPlusTotal += sessionDuration
                        }
                    }
                    currentSessionStart = nil
                }
            }
        }
        
        // 处理最后一个未结束的会话
        if let sessionStart = currentSessionStart {
            let sessionDuration = endDate.timeIntervalSince(sessionStart)
            if sessionDuration >= 30 * 60 {
                thirtyMinPlusTotal += sessionDuration
                if sessionDuration >= 60 * 60 {
                    sixtyMinPlusTotal += sessionDuration
                }
            }
        }
        
        let stats = HeartRateIntervalStats(totalMediumZoneDuration: totalDuration,
                                           durationWith30MinPlusSessions: thirtyMinPlusTotal,
                                           durationWith60MinPlusSessions: sixtyMinPlusTotal)
        completion(stats, nil)
    }
    
    healthStore.execute(query)
}

使用示例

let healthManager = HealthKitManager()

// 1. 请求权限
healthManager.requestHeartRateAuthorization { success, error in
    guard success else { return }
    
    // 2. 获取用户年龄,计算最大心率
    healthManager.getUserAge { age in
        guard let age = age else { return }
        let maxHR = healthManager.calculateMaxHeartRate(age: age)
        let targetDate = Date() // 目标日期
        
        // 3. 获取单日平均心率
        healthManager.getDailyAverageHeartRate(forDate: targetDate) { avgHR, error in
            if let avgHR = avgHR {
                print("当日平均心率:\(avgHR) bpm")
            }
        }
        
        // 4. 获取中等心率区间统计
        healthManager.getMediumHeartRateZoneStats(forDate: targetDate, maxHeartRate: maxHR) { stats, error in
            if let stats = stats {
                print("中等区间总时长:\(stats.totalMediumZoneDuration/60) 分钟")
                print("单次≥30分钟累计时长:\(stats.durationWith30MinPlusSessions/60) 分钟")
                print("单次≥60分钟累计时长:\(stats.durationWith60MinPlusSessions/60) 分钟")
            }
        }
    }
}

关键说明

  • 心率样本连续性:实际场景中,若心率样本间隔超过阈值(如1分钟),可视为会话中断,需调整代码中的连续性判断逻辑。
  • 单位一致性:全程使用count/min单位处理心率值,避免单位不匹配导致计算错误。
  • 错误处理:实际使用中需完善权限未授权、无数据等分支的错误提示逻辑。

内容的提问来源于stack exchange,提问作者AaoIi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 19:50:19