WatchKit Controller Life Cycle

Update on 5/19/2015: Be sure to read about the Watch OS 1.0.1 Controller Life Cycle Changes

There seem to be a lot of questions about the order of WKInterfaceController events and how those events relate to the NSExtensionContext notifications I referenced in my WatchKit Development Tips. Hopefully, this post will bring some clarity to the WatchKit controller life cycle.

The example I’ll use features two interface controllers in a page-based layout. However, the same general principles apply to hierarchical interfaces, Glances, and Notification interfaces.

When multiple page-based interface controllers are loaded (whether they’re the root interfaces or they’ve been presented modally), init and awakeWithContext: are called on all controllers before willActivate is called on the first page. This means that you can improve the apparent responsiveness of your app by doing as little work as necessary in these two methods. As it’s a common question, be aware that awakeWithContext: will not be called again.

While there are no hard-and-fast rules about the work you should perform in init vs. awakeWithContext:, awakeWithContext: is the earliest that you have access to the context. Because of this, you could decide to perform general initialization in init and more context-specific work in awakeWithContext:. Note that you can configure interface elements in both of these methods.

After an interface controller is initialized and passed its context, the only time it can update its interface is while the interface is active. This means that you can perform updates in willActivate (when the interface becomes active) and at any point until didDeactivate is called. You cannot, however, update the interface within didDeactivate, as the interface is no longer considered active.

The following diagram illustrates two page-based interface controllers running on actual Apple Watch hardware. I mention this, because the NSExtensionContext notifications that you see here are not delivered in the simulator.

After the app starts, you can see that both interface controllers are given a chance to initialize and configure their contexts. It’s important to note that all of the calls in this diagram occur on the main thread.

Next, you see two NSExtensionContext notifications, NSExtensionHostWillEnterForegroundNotification and  NSExtensionHostDidBecomeActiveNotification. Normally, these two notifications are very reliable. In my testing, though, I’ve noticed that—even if you register as an observer in your interface controller’s initialize class method—you might not catch these first two notifications. So, you need to plan accordingly.

Next, I swipe to Page 2. Note that willActivate is called on the second interface controller before didDeactivate is called on the first. This is contrary to the following statement in the Apple Watch Programming Guide:

During a transition, the currently visible interface controller’s didDeactivate method is called, followed by a call to the willActivate method of the interface controller that is about to be displayed.

This bit of documentation appears to be incorrect or out-of-date based on my experience with both the simulator and actual hardware.

Side note: This same sequence occurs when presenting a modal interface. That is, willActivate is called on the presented controller, followed by a call to didDeactivate on the presenting controller. When the modal interface is dismissed, again, willActivate is called on the presenting controller, followed by a call to didDeactivate on the presented (now dismissed) controller. Finally, as you’d expect, if there is no longer a strong reference to the now-deactivated controller, it is deallocated (from a different thread, by the way).

Back to our two-page example. If I drop my arm, you can see that two notifications are delivered: NSExtensionHostWillResignActiveNotification and NSExtensionHostDidEnterBackgroundNotification. Last, didDeactivate is called on the second interface controller before the app is suspended (the Watch does reserve the right to terminate your app).

After waiting a few seconds, I raise my arm to wake up the app, and you can see the two NSExtensionContext notifications before willActivate is called on our second interface controller. This pattern continues as controllers are pushed, popped, presented, and dismissed.

The life cycle that we’ve explored here is the same for hierarchical interfaces, Glances, and Notification interfaces, so you can take advantage of these events in all of those situations.

Happy coding!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *