Call external function using WatchKit force touch MenuItem
healthkit, swift, watchkit
Solution
The fatal error is (or was) caused by this line:
`let wSM = WorkoutSessionManager()`
That line creates a new instance of WorkoutSessionManager and calls `init()` on it.
Swift provides a default initializer called `init()` for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. But WorkoutSessionManager does not provide default values for the `healthStore` and `workoutSession` properties (and those properties are not optionals), and it provides its own initializer named `init(context:)`, so it has no default initializer.
You need to either create your instance of WorkoutSessionManager using the designated initializer `init(context:)` (passing an appropriate instance of WorkoutSessionContext) or provide a default initializer for WorkoutSessionManager named `init()`.
The precise manner in which you should do the former depends on the implementation of the rest of your app and the presentation of your DashboardController. I assume you are trying to recreate the "Fit" app shown in WWDC 2015 Session 203.
In that demonstration, the initial controller is an instance of ActivityInterfaceController, and that controller is responsible for presenting the next interface (via segues created in the storyboard). You can see the following code in the ActivityInterfaceController class:
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
let activityType: HKWorkoutActivityType
switch segueIdentifier {
case "Running":
activityType = .Running
case "Walking":
activityType = .Walking
case "Cycling":
activityType = .Cycling
default:
activityType = .Other
}
return WorkoutSessionContext(healthStore: self.healthStore, activityType: activityType)
}
The function above creates and returns a new instance of WorkoutSessionContext using an instance of HKHealthStore held by the initial controller. The context returned by that function is passed to the destination interface controller for the relevant segue through `awakeWithContext`.
For transitions in code, you can pass a context instance using equivalent functions such as `pushControllerWithName(context:)` which also lead to `awakeWithContext`.
If your initial controller is similar to the above, you can access the passed context in `awakeWithContext` in your DashboardController class and use it to configure a new instance of WorkoutSessionManager:
class DashboardController: WKInterfaceController
{
// ...
var wSM: WorkoutSessionManager?
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
if context is WorkoutSessionContext {
wSM = WorkoutSessionManager(context: context as! WorkoutSessionContext)
}
addMenuItemWithItemIcon(.Accept, title: "Save", action: #selector(DashboardController.saveSession))
}
// ...
}
Creating an instance of WorkoutSessionManager in that way avoids calling the (non-existent) `init()` initializer and permits reuse of the HKHealthStore instance. Whether that approach is open to you depends on the rest of your code and the way you are presenting your DashboardController.
Note that you should avoid creating multiple instances of WorkoutSessionManager. Use a singleton to provide a single instance of WorkoutSessionManager that is shared across your extension.
Problem
I need to implement a WatchKit force-touch `MenuItem` to call a `saveWorkout()` method that is located in a separate class that does not subclass `WKInterfaceController`. I realize that every class needs at least one designated initializer. I am guessing this is the key? Btw, my "saveSession() reached" print statement logs to the console when using the sim but not when I use a device. All other print statements log to the console even when using the device. A bit odd. My attempts at initialization throw various errors such as: 1.fatal error: use of unimplemented initializer 'init()' for class 'DashboardController' 2.Missing argument for parameter 'context' in call Dashboard.swift ``` class DashboardController: WKInterfaceController { @IBOutlet var timerLabel: WKInterfaceTimer! @IBOutlet weak var milesLabel: WKInterfaceLabel! // var wSM: WorkoutSessionManager //init(wSM: WorkoutSessionManager) { // self.wSM = wSM // super.init() // } override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) addMenuItemWithItemIcon(.Accept, title: "Save", action: #selector(DashboardController.saveSession)) } override func willActivate() { super.willActivate() print("Dashboard controller reached") } func saveSession() { //wSM.saveWorkout() print("saveSession() reached") } ``` WorkoutSessionManager.swift ``` class WorkoutSessionContext { let healthStore: HKHealthStore let activityType: HKWorkoutActivityType let locationType: HKWorkoutSessionLocationType init(healthStore: HKHealthStore, activityType: HKWorkoutActivityType = .Other, locationType: HKWorkoutSessionLocationType = .Unknown) { self.healthStore = healthStore self.activityType = activityType self.locationType = locationType } } protocol WorkoutSessionManagerDelegate: class { // ... protocol methods } class WorkoutSessionManager: NSObject, HKWorkoutSessionDelegate { let healthStore: HKHealthStore let workoutSession: HKWorkoutSession init(context: WorkoutSessionContext) { self.healthStore = context.healthStore self.workoutSession = HKWorkoutSession(activityType: context.activityType, locationType: context.locationType) self.currentActiveEnergyQuantity = HKQuantity(unit: self.energyUnit, doubleValue: 0.0) self.currentDistanceQuantity = HKQuantity(unit: self.distanceUnit, doubleValue: 0.0) super.init() self.workoutSession.delegate = self } func saveWorkout() { guard let startDate = self.workoutStartDate, endDate = self.workoutEndDate else {return} // ...code... ```