tymac tymac - 1 year ago 84
Swift Question

Call external function using WatchKit force touch MenuItem

I need to implement a WatchKit force-touch

to call a
method that is located in a separate class that does not subclass

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


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?) {

addMenuItemWithItemIcon(.Accept, title: "Save", action: #selector(DashboardController.saveSession))

override func willActivate() {
print("Dashboard controller reached")

func saveSession() {
print("saveSession() reached")


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)


self.workoutSession.delegate = self

func saveWorkout() {
guard let startDate = self.workoutStartDate, endDate = self.workoutEndDate else {return}

// ...code...

Answer Source

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

        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?) {

        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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download