ios 训练路线的HKSampleQuery不返回任何样本,即使样本存在

nuypyhwy  于 2023-05-02  发布在  iOS
关注(0)|答案(1)|浏览(137)

概述

我有2个自行车训练(使用Apple Watch SE上的训练应用程序记录),我正在尝试从中检索位置数据(GPX样本)。他们是同一个连续乘坐的一部分,我记录使用2个单独的锻炼,由于原来的锻炼无法中途取消暂停。最后,我想获得所有位置样本并将它们合并到一个文件中。

当前状态

我的iPhone(SE,2020)上的健康应用程序正确地显示了我记录当天(7月16日)的2项锻炼,并且还包含2项锻炼的4个锻炼路线对象:1个组成第一次锻炼的全部,3个合并组成第二次锻炼。
Screenshot showing 2 workouts
Screenshot showing 4 workout routes
然而,当我导出原始健康数据时,仅显示7月16日的3条锻炼路线。他们都是为了第二次训练。第一次训练不会导出GPX路径文件。

尝试求解

为了尝试直接从HealthKit访问原始数据,我找到了一个app on Github,构建了它,并将其加载到我的iPhone上,看看我能得到什么。应用程序检索训练列表,并在选择训练时导出包含所有位置样本的GPX文件。这与我的第二次骑自行车锻炼效果很好,但在试图导出第一次锻炼的数据时崩溃了。
以下是实际查询训练路线信息的函数:

public func route(for workout: HKWorkout, completion: @escaping (([CLLocation]?, Error?) -> Swift.Void)) {
    let routeType = HKSeriesType.workoutRoute();
    let p = HKQuery.predicateForObjects(from: workout)
    let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)
    
    let q = HKSampleQuery(sampleType: routeType, predicate: p, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) {
        (query, samples, error) in
        if let err = error {
            print(err)
            return
        }
        
        guard let routeSamples: [HKWorkoutRoute] = samples as? [HKWorkoutRoute] else { print("No route samples"); return }

        if (routeSamples.count == 0){
            completion([CLLocation](), nil)
            return;
        }
        var sampleCounter = 0
        var routeLocations:[CLLocation] = []

        for routeSample: HKWorkoutRoute in routeSamples {
            
            let locationQuery: HKWorkoutRouteQuery = HKWorkoutRouteQuery(route: routeSample) { _, locationResults, done, error in
                guard locationResults != nil else {
                    print("Error occured while querying for locations: \(error?.localizedDescription ?? "")")
                    DispatchQueue.main.async {
                        completion(nil, error)
                    }
                    return
                }

                if done {
                    sampleCounter += 1
                    if sampleCounter != routeSamples.count {
                        if let locations = locationResults {
                            routeLocations.append(contentsOf: locations)
                        }
                    } else {
                        if let locations = locationResults {
                            routeLocations.append(contentsOf: locations)
                            let sortedLocations = routeLocations.sorted(by: {$0.timestamp < $1.timestamp})
                            DispatchQueue.main.async {
                                completion(sortedLocations, error)
                            }
                        }
                    }
                } else {
                    if let locations = locationResults {
                        routeLocations.append(contentsOf: locations)
                    }
                }
            }
            
            self.healthStore.execute(locationQuery)
        }
    }
    healthStore.execute(q)
}

进一步调试后,在查询第一个自行车训练时,HKSampleQuery调用似乎返回0个样本。当查询第二个骑车训练时,它返回3个样本(每个训练路线对象)。
因此,就好像HealthKit API无法看到第四条锻炼路线,尽管它明显存在,如健康应用程序的屏幕截图所示。

问题

这是怎么回事?为什么第二次锻炼返回的数据很好,而第一次没有?
是否可以直接查询健康数据库来获得这些数据?我有一个本地加密的iPhone备份,有一个我也可以访问的已知密码。
这次骑行对我来说很重要,所以我在尽我所能检索位置数据。先谢谢你了!
编辑:添加调用route()函数的主代码。

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath);
        guard let workouts = self.workouts else {
            return;
        }

        if (indexPath.row >= workouts.count){
            return;
        }

        print(indexPath.row)
        let workout = workouts[indexPath.row];
        let workout_name: String = {
            switch workout.workoutActivityType {
                case .cycling: return "Cycle"
                case .running: return "Run"
                case .walking: return "Walk"
                default: return "Workout"
            }
        }()
        let workout_title = "\(workout_name) - \(self.dateFormatter.string(from: workout.startDate))"
        let file_name = "\(self.filenameDateFormatter.string(from: workout.startDate)) - \(workout_name)"

        let targetURL = URL(fileURLWithPath: NSTemporaryDirectory())
            .appendingPathComponent(file_name)
            .appendingPathExtension("gpx")

        let file: FileHandle

        do {
            let manager = FileManager.default;
            if manager.fileExists(atPath: targetURL.path){
                try manager.removeItem(atPath: targetURL.path)
            }
            print(manager.createFile(atPath: targetURL.path, contents: Data()))
            file = try FileHandle(forWritingTo: targetURL);
        } catch let err {
            print(err)
            return
        }

        workoutStore.heartRate(for: workouts[indexPath.row]){
            (rates, error) in

            guard let keyedRates = rates, error == nil else {
                print(error as Any);
                return
            }

            let iso_formatter = ISO8601DateFormatter()
            var current_heart_rate_index = 0;
            var current_hr: Double = -1;
            let bpm_unit = HKUnit(from: "count/min")
            var hr_string = "";
            file.write(
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?><gpx version=\"1.1\" creator=\"Apple Workouts (via pilif's hack of the week)\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.topografix.com/GPX/1/1\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" xmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v1\"><trk><name><![CDATA[\(workout_title)]]></name><time>\(iso_formatter.string(from: workout.startDate))</time><trkseg>"
                        .data(using: .utf8)!
            )

            self.workoutStore.route(for: workouts[indexPath.row]){
                (maybe_locations, error) in
                guard let locations = maybe_locations, error == nil else {
                    print(error as Any);
                    file.closeFile()
                    return
                }

                for location in locations {
                    while (current_heart_rate_index < keyedRates.count) && (location.timestamp > keyedRates[current_heart_rate_index].startDate) {
                        current_hr = keyedRates[current_heart_rate_index].quantity.doubleValue(for: bpm_unit)
                        current_heart_rate_index += 1;
                        hr_string = "<extensions><gpxtpx:TrackPointExtension><gpxtpx:hr>\(current_hr)</gpxtpx:hr></gpxtpx:TrackPointExtension></extensions>"
                    }

                    file.write(
                        "<trkpt lat=\"\(location.coordinate.latitude)\" lon=\"\(location.coordinate.longitude)\"><ele>\(location.altitude.magnitude)</ele><time>\(iso_formatter.string(from: location.timestamp))</time>\(hr_string)</trkpt>"
                            .data(using: .utf8)!
                    )
                }
                file.write("</trkseg></trk></gpx>".data(using: .utf8)!)
                file.closeFile()

                let activityViewController = UIActivityViewController( activityItems: [targetURL],
                                                                       applicationActivities: nil)
                if let popoverPresentationController = activityViewController.popoverPresentationController {
                    popoverPresentationController.barButtonItem = nil
                }
                self.present(activityViewController, animated: true, completion: nil)
            }
        }
    }
zujrkrfu

zujrkrfu1#

你说健康显示数据,健身呢?如果您可以在“健身”中看到该路径,则该路径存在,您应该能够提取该路径。
假设您从route()中的guard语句中获得“No route samples”,则应验证您正在通过的训练。此外,您可以更改 predicate p以指定锻炼。开始和锻炼。末日
你也可以在guard上面插入一个临时的debug print语句来查看返回的内容:
print(“samples:(String(描述:样品))”)。

相关问题