1

Most of the HealthKit sample code uses an InterfaceController to serve as a HKWorkoutSessionDelegate however I'm trying to set up a HealthStoreManager class that will serve as a HKWorkoutSessionDelegate and take this business logic away from any of my individual InterfaceControllers responsibility, however the below code fails to start a workout, can anyone see my error?

 InterfaceController: {

         func segueToWorkoutInterfaceControllerWithContext() {

                // Create workout configuration
                let workoutConfiguration = HKWorkoutConfiguration()

                workoutConfiguration.activityType = .running
                workoutConfiguration.locationType = .outdoor  

                do {
                    let workoutSession = try HKWorkoutSession(configuration: workoutConfiguration)
                    let healthStoreManager = HealthStoreManager()
                    workoutSession.delegate = healthStoreManager
                    healthStoreManager.start(workoutSession)
                } catch {
                    fatalError(error.localizedDescription)
                }


                let contextDictionary = ["workoutConfiguration" : workoutConfiguration, "ActivityType": selectedActivityType] as [String : Any]

                // Pass configuration to next interface controller
                WKInterfaceController.reloadRootPageControllers(withNames: ["WorkoutControlsInterfaceController", "MainDisplayInterfaceController", "SpeedInterfaceController", "CaloriesAndDistanceInterfaceController"],
                                                                contexts: [contextDictionary],
                                                                orientation: .horizontal,
                                                                pageIndex: 1)
            }

        }
    }


class HealthStoreManager: NSObject, CLLocationManagerDelegate, HKWorkoutSessionDelegate {
 private let healthStore = HKHealthStore()

// MARK: - HKWorkoutSessionDelegate

    func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
        print("workout session did fail with error: \(error)")
    }

    func workoutSession(_ workoutSession: HKWorkoutSession,
                        didChangeTo toState: HKWorkoutSessionState,
                        from fromState: HKWorkoutSessionState,
                        date: Date) {
        DispatchQueue.main.async {
            self.handleWorkoutSessionState(didChangeTo: toState, from: fromState)
        }
    }

    func workoutSession(_ workoutSession: HKWorkoutSession, didGenerate event: HKWorkoutEvent) {
        DispatchQueue.main.async {
            //self.healthStoreManager.workoutEvents.append(event)
        }
    }

    private func handleWorkoutSessionState(didChangeTo toState: HKWorkoutSessionState,
                                           from fromState: HKWorkoutSessionState) {
        switch (fromState, toState) {
        case (.notStarted, .running):

            print("workout started")

        case (_, .ended):


        default:
            break
        }


    }


}
GarySabo
  • 5,806
  • 5
  • 49
  • 124

1 Answers1

2

I found my error, I needed to pass the healthStoreManager instance into the context in order to avoid this class from being deallocated:

  let contextDictionary = ["workoutConfiguration" : workoutConfiguration, "ActivityType": selectedActivityType, "healthStoreManager" : healthStoreManager] as [String : Any]
GarySabo
  • 5,806
  • 5
  • 49
  • 124
  • I had always assumed that it wasn't necessary to keep a strong reference to `HKHealthStore` in general. Thanks for documenting this! – Mike Mertsock Apr 26 '18 at 13:34
  • Sure thing! Some sample codes uses a singleton for a HealthManager class (and the `HKHealthStore` object it owns), but in my opinion a user may be doing back to back workouts so I think it's better to have a new manager/store class init for each workout, rather than passing data back and forth to the same object. – GarySabo Apr 26 '18 at 15:19