如何在Swift中从HealthKit获取无重叠的睡眠样本?
解决睡眠样本重叠问题的方法
要获取无重叠的正确睡眠数据,你需要对筛选后的inBed样本进行排序并合并重叠/相邻的时间区间,具体实现如下:
核心思路
- 先筛选出所有
inBed类型的睡眠样本 - 按开始时间升序排序样本,确保从最早的睡眠时段开始处理
- 遍历排序后的样本,合并重叠或相邻的时间区间,避免重复计算重叠部分
修改后的完整代码
func readSleep(from startDate: Date?, to endDate: Date?, id: Int, completion: @escaping (Double, Date, Date, Int, String) -> Void) { let healthStore = HKHealthStore() guard let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis), let startDate = startDate, let endDate = endDate else { completion(0.0, startDate ?? Date(), endDate ?? Date(), id, "") return } let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictEndDate) // 改为按开始时间升序排序,为合并区间做准备 let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true) let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) { (query, result, error) in if let error = error { print("获取睡眠数据失败: \(error.localizedDescription)") completion(0.0, startDate, endDate, id, "") return } guard let sleepSamples = result as? [HKCategorySample] else { completion(0.0, startDate, endDate, id, "") return } // 筛选出inBed类型的样本 let inBedSamples = sleepSamples.filter { sample in guard let sleepValue = HKCategoryValueSleepAnalysis(rawValue: sample.value) else { return false } return sleepValue == .inBed } guard !inBedSamples.isEmpty else { completion(0.0, startDate, endDate, id, "") return } // 合并重叠/相邻的时间区间 var mergedIntervals: [(start: Date, end: Date, source: String)] = [] var currentStart = inBedSamples[0].startDate var currentEnd = inBedSamples[0].endDate var currentSource = inBedSamples[0].sourceRevision.source.name for sample in inBedSamples.dropFirst() { if sample.startDate <= currentEnd { // 区间重叠或相邻,合并为一个区间 currentEnd = max(currentEnd, sample.endDate) // 若需要保留所有来源,可改为拼接字符串,示例中取第一个来源 // currentSource = "\(currentSource), \(sample.sourceRevision.source.name)" } else { // 新的独立区间,存入结果列表 mergedIntervals.append((currentStart, currentEnd, currentSource)) currentStart = sample.startDate currentEnd = sample.endDate currentSource = sample.sourceRevision.source.name } } // 存入最后一个合并后的区间 mergedIntervals.append((currentStart, currentEnd, currentSource)) // 计算总睡眠时长并返回每个合并后的区间数据 var totalSleepHours = 0.0 for interval in mergedIntervals { let durationSeconds = interval.end.timeIntervalSince(interval.start) let durationHours = durationSeconds / 3600 totalSleepHours += durationHours completion(durationHours, interval.start, interval.end, id, interval.source) } } healthStore.execute(query) }
关键细节说明
- 排序调整:将原代码按结束时间降序改为按开始时间升序,确保合并区间的顺序正确
- 合并逻辑:通过比较当前样本与已合并区间的时间关系,自动合并重叠部分,避免重复计算
- 来源处理:示例中保留了睡眠数据的来源名称,可根据需求调整为拼接多个来源或优先保留特定来源
内容的提问来源于stack exchange,提问作者Sowrya mukkavilli
相关产品推荐
相关产品推荐

