Type Safety for Interface Controller Identifiers in WatchKit

Rudrank Riyam
3 min readMar 18, 2021
Photo by Daniel Cañibano on Unsplash

I’ve been working with WatchKit lately and was rewriting some old code for better navigation handling.

When creating a watchOS app using WatchKit, we use Interface Builder for creating WKInterfaceController.

For identifying that particular screen while handling the navigation programmatically, we provide the custom class for it and the identifier associated with that screen.

Here, we use hard-coded strings for the identifier, and I sometimes mistype them in code while presenting/pushing the controller or reloading them. So I used a more type-safe approach of using enums and an extension on WKInterfaceController that I want to share.

I’m creating a clone of the workout app on the Apple watch. I start with the first screen, which lists the type of workouts in a carousel style.

Let’s name this controller as WorkoutListInterfaceController.

When clicking on any of the activities, it opens a timer screen which opens three paged screens. Those screens contain the details of the workout, controls for it, and the now playing screen.

I name the timer controller as WorkoutCountdownInterfaceController.

The page style screens can be named as WorkoutControlsInterfaceController, WorkoutStatisticsInterfaceController, and NowPlayingInterfaceController respectively.

These will be the identifiers for defining the WKInterfaceController in Interface Builder and identifying them while writing code.

When you click on a particular workout, it reloads the screen with a single screen controller for showing the timer.

The timer screen after the countdown reloads the screen with page-based controllers.

For that, we use the method— reloadRootPageControllers(withNames:contexts:orientation:pageIndex:)

The standard approach would be calling this function as -

WKInterfaceController.reloadRootPageControllers(withNames: ["WorkoutControlsInterfaceController", "WorkoutStatisticInterfaceController", "NowPlayingInterfaceController"], contexts: [context], orientation: .horizontal, pageIndex: 1)

And oops. This gives an error-

Error — interface does not define view controller class WorkoutStatisticInterfaceController

The error happened because I mistyped the name of the identifier. And the probability of such mistakes happening increases when you have 10–20 of such controllers.

So, let’s try a more type-safe way using enum!

I start with an enum InterfaceIdentifier and add identifiers as cases.

If I have to be honest, the primary reason for working on making navigation easier was to get the dot notation.

I wrote a method to reload one single root screen and page-paged screens that take InterfaceControllerName as a parameter.

I used three examples in my sample project:

  • For going from the workout list screen to the timer screen, I use —

reload(withName: .workoutCountdown)

  • For proceeding from the timer screen to the main workout screens, I use —

reload(withNames: [.workoutControls, .workoutStatistics, .nowPlaying], context: context, pageIndex: 1)

  • For ending the workout and going back to the home screen, I use —

reload(withName: .workoutList, context: context)

I feel this eliminated all the bugs related to mistyping identifiers, and I also love dot notation for getting the list of all available controllers.

I’m still a beginner in this massive Apple platforms development world, so any feedback or a better solution is much appreciated in the comment section!

--

--

Rudrank Riyam

Apple Platforms Developer. Technical Writer & Author. Conference Speaker. WWDC '19 Scholar.