Невозможно запросить дистанцию ​​плавания и количество ходов плавания из тренировки - PullRequest
0 голосов
/ 21 февраля 2020

Невозможно запросить расстояние плавания и количество ходов плавания из сеанса тренировки.

У меня есть все необходимые разрешения HealthKit для чтения / записи, начиная сеанс тренировки с использованием activityType = swim и swimLocationType = openWater, но делегат HKLiveWorkoutBuilder не делает не запускают события для дистанции плавания или количества ударов, только для частоты сердечных сокращений, сожженной энергии и т. д. c ... Но после окончания тренировки количество ударов в приложении Health иногда увеличивается.

Тестирование на часах Series 5 / WatchOS 6.1.3

Вот мои основные настройки c

    private var locationManager: CLLocationManager?
    private var motionManager: CMMotionManager?
    private var session: HKWorkoutSession!
    private var builder: HKLiveWorkoutBuilder!
    private var route: HKWorkoutRouteBuilder!
    private var timer: Timer?
    private var activeQueries = [HKQuery]()

    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        setup()
    }

    private func setup() {
        locationManager = CLLocationManager()
        locationManager?.desiredAccuracy = kCLLocationAccuracyBest
        locationManager?.activityType = .other
        locationManager?.distanceFilter = kCLDistanceFilterNone
        locationManager?.allowsBackgroundLocationUpdates = true
        locationManager?.delegate = self
        locationManager?.startUpdatingLocation()

        motionManager = CMMotionManager()

        let configuration = HKWorkoutConfiguration()
        configuration.activityType = .swimming
        configuration.swimmingLocationType = .openWater
        configuration.locationType = .outdoor


        do {
            session = try HKWorkoutSession(healthStore: HealthStoreManager.shared.store, configuration: configuration)
            builder = session.associatedWorkoutBuilder()
        } catch {
            doError(message: error.localizedDescription)
            return
        }

        route = HKWorkoutRouteBuilder(healthStore: HealthStoreManager.shared.store, device: nil)

        session.delegate = self
        builder.delegate = self

        let dataSource = HKLiveWorkoutDataSource(healthStore: HealthStoreManager.shared.store, workoutConfiguration: configuration)
        dataSource.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .heartRate)!, predicate: nil)
        dataSource.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .basalEnergyBurned)!, predicate: nil)
        dataSource.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!, predicate: nil)
        dataSource.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .swimmingStrokeCount)!, predicate: nil)
        dataSource.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .distanceSwimming)!, predicate: nil)
        builder.dataSource = dataSource

        session.startActivity(with: Date())
    }

    private func startQueries() {
        motionManager?.startDeviceMotionUpdates(to: .main, withHandler: { (motion, error) in
            guard let motion = motion, error == nil else {
                return
            }

            print(motion)
        })
    }

    private func reset() {
        activeQueries.forEach{ HealthStoreManager.shared.store.stop($0) }
        activeQueries.removeAll()
        timer?.invalidate()
        locationManager?.stopUpdatingLocation()
        session.delegate = nil
        builder.delegate = nil
        builder.dataSource = nil
        builder = nil
        session = nil
        route = nil
    }

    private func doError(message: String? = nil) {
        if let message = message {
            print("[WORKOUT] ‼️ \(message)")
        }

        reset()
        DispatchQueue.main.async {
            WKInterfaceController.reloadRootPageControllers(withNames: ["home"], contexts: nil, orientation: .horizontal, pageIndex: 0)
        }
    }

    @IBAction func stopTouched() {
        timer?.invalidate()
        locationManager?.stopUpdatingLocation()
        motionManager?.stopDeviceMotionUpdates()
        activeQueries.forEach{ HealthStoreManager.shared.store.stop($0) }

        guard session.state == .running else {
            return
        }

        self.builder.endCollection(withEnd: Date()) { (success, error) in
            self.builder.finishWorkout { (workout, error) in
                guard let workout = workout, error == nil else {
                    self.session.end()
                    return
                }

                self.route.finishRoute(with: workout, metadata: nil) { (success, error) in
                    print("[WORKOUT][\(#function)] ✅ Saved")
                    self.session.end()
                }
            }
        }

    }

    @objc private func runTimer() {
        let startDate = builder.startDate ?? Date()
        let components = Calendar.current.dateComponents([Calendar.Component.hour, Calendar.Component.minute, Calendar.Component.second], from: startDate, to: Date())
        let time = String(format: "%02d:%02d:%02d", components.hour ?? 0, components.minute ?? 0, components.second ?? 0)
    }
}

// MARK: - Workout Delegate
// ----------------------------------------------------------------------------

extension SessionController: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {
    func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
        switch (fromState, toState) {
        case (.notStarted, .running):
            builder.beginCollection(withStart: Date()) { (success, error) in
                DispatchQueue.main.async {
                    self.startQueries()
                    self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.runTimer), userInfo: nil, repeats: true)
                    WKExtension.shared().enableWaterLock()
                }
            }
        case(.running, .ended):
            self.reset()
            DispatchQueue.main.async {
                WKInterfaceController.reloadRootPageControllers(withNames: ["home", "sessions", "settings"], contexts: nil, orientation: .horizontal, pageIndex: 0)
            }
        default:
            break;
        }
    }

    func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
        print("[WORKOUT][\(#function)] \(error.localizedDescription)")
    }

    func workoutSession(_ workoutSession: HKWorkoutSession, didGenerate event: HKWorkoutEvent) {
        print("[WORKOUT] \(event)")
    }

    func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
        for type in collectedTypes {
            guard let quantityType = type as? HKQuantityType else {
                return
            }

            let statistics = workoutBuilder.statistics(for: quantityType)

            // distanceSwimming and swimmingStrokeCount are never fired
            switch quantityType {
            case HKQuantityType.quantityType(forIdentifier: .distanceSwimming):
                let value = statistics?.mostRecentQuantity()?.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.meter())) ?? 0.0
                print(value)
            case HKQuantityType.quantityType(forIdentifier: .swimmingStrokeCount):
                let value = statistics?.mostRecentQuantity()?.doubleValue(for: HKUnit.count()) ?? 0.0
                print(value)
            default:
                return
            }
        }
    }

    func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
    }
}

// MARK: - CLLocationManagerDelegate
// ----------------------------------------------------------------------------

extension SessionController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        route.insertRouteData(locations) { (success, error) in
            if success {
                print("[WORKOUT][\(#function)] inserted route")
            }
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("[WORKOUT] \(#function) \(error.localizedDescription)")
    }
}
...