swifthealthkithkhealthstorewatchos-5

Attempting to start HKLiveWorkout keep getting unexpectedly found nil on session


Im trying to make a workout app where the user's heart rate is being displayed on the Apple Watch. I've been following Apple's WWDC "New Ways to Work with workouts" vide. Here's the link https://developer.apple.com/videos/play/wwdc2018/707/?time=615

Anyway, every time I try to run the app I keep getting the error "Thread 1: Fatal Error unexpectedly found nil while unwrapping an optional value "

 session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)

I've tried adding a a question mark(?) after the "try" but all that does is prevent the app from crashing and doesn't start the workout. Here's the full code. P.S. I am fairly new to Swift and I'm finding it incredibly frustrating that there isnt much example code for the new HealthKit yet. (I know its fairly new, but still frustrating :D). Thanks for the help

class InterfaceController: WKInterfaceController, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {

    let healthStore = HKHealthStore()
    var configuration: HKWorkoutConfiguration!

    var session: HKWorkoutSession!
    var builder: HKLiveWorkoutBuilder!


func startWorkoutWithHealthStore(){

      //  configuration.activityType = .crossTraining
    //    configuration.locationType = .indoor

        do {
            session = try? HKWorkoutSession(healthStore: healthStore, configuration: configuration)
        } catch {
            // let the user know about the error
            return
        }

        builder = session.associatedWorkoutBuilder()


        //Setup session and builder

        session.delegate = self
        builder.delegate = self

        builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, workoutConfiguration: configuration)


                //Start Session & Builder

        session.startActivity(with: Date())


        builder.beginCollection(withStart: Date()) { (success, error) in
            self.setDurationTimerDate() //Start the elapsed time timer
        }

    }

    @IBAction func startButtonClicked() {

        print("Start BTN clicked")
        startWorkoutWithHealthStore()

    }

    //Track Elapsed Time
    func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder){

        print("Collection Started")
        setDurationTimerDate()

    }

    func setDurationTimerDate(){
        print(", duration timer started"
        )
        //Create WKInterfaceTimer Date
        let timerDate = Date(timeInterval: -self.builder.elapsedTime, since: Date())
        DispatchQueue.main.async {
            self.timer.setDate(timerDate)
        }
        //Start or stop timer
        let sessionState = self.session.state
        DispatchQueue.main.async {
            sessionState == .running ? self.timer.start() : self.timer.stop()
        }
    }

    // MARK: HKLiveWorkoutBuilderDelegate
    func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>){

        for type in collectedTypes{

            guard let quantityType = type as? HKQuantityType else {
                return // Do nothing
            }


            let statistics = workoutBuilder.statistics(for: quantityType)
            //let label = labelForQuantityType(quantityType)

           // updateLabel(wkLabel, withStatistics: statistics)

            print(statistics as Any)
        }


    }

    // MARK: State Control
    func stopWorkout(){

        session.end()
        builder.endCollection(withEnd: Date()) { (success, error) in

            self.builder.finishWorkout(completion: { (workout, error) in
                self.dismiss()
            })

        }
    }


}

Solution

  • You shouldn't mask the error into an Optional by using try? thrown by HKWorkoutSession(healthStore: healthStore, configuration: configuration), especially if you already put the statement in a do-catch block. You get a crash, because session is defined as an implicitly unwrapped optional (! mark after the type), which it shouldn't be.

    You should define session as a normal optional if it might have a nil value and safely unwrap/optional chain it every time you access it.

    class InterfaceController: WKInterfaceController, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {
    
        let healthStore = HKHealthStore()
        let configuration = HKWorkoutConfiguration()
    
        var session: HKWorkoutSession? = nil
        var builder: HKLiveWorkoutBuilder? = nil
    
    
        func startWorkoutWithHealthStore(){
            configuration.activityType = .crossTraining
            configuration.locationType = .indoor
    
            do {
                session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
            } catch {
                print(error)
                session = nil
                return
            }
    
            builder = session?.associatedWorkoutBuilder()
    
    
            //Setup session and builder
    
            session?.delegate = self
            builder?.delegate = self
    
            builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, workoutConfiguration: configuration)
    
    
            //Start Session & Builder
            session?.startActivity(with: Date())
    
    
            builder?.beginCollection(withStart: Date()) { (success, error) in
                self.setDurationTimerDate() //Start the elapsed time timer
            }
    
        }
    ...
    }