Невозможно запросить расстояние плавания и количество ходов плавания из сеанса тренировки.
У меня есть все необходимые разрешения 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)")
}
}