For some reason, after watching WWDC talks mentioning watchOS in the last few years, I had this image in my mind that almost every version changed everything in how apps are built. I remembered something about native and non-native apps, two different types of app schemes in Xcode, and some diagrams of pieces moving from one box to another, on more than one occasion. This all sounded confusing, and I think that’s one reason why I was discouraged from starting, because I imagined it would make it hard for me to catch up with all of that.
As it turned out, this wasn’t really true.
The only change that could be considered major was the move from watchOS 1.0 to 2.0 – it introduced what’s called native apps (somewhat confusingly – since both kinds of apps are native in the usual sense, that they’re written using ObjC and Swift, and not web technologies or some other kind of emulation). But even that, as I understand, was really a smaller change than it appears (though I won’t know for sure). The general architecture, the way you write Watch apps, everything that’s different than what you’re used to from iOS – this all hasn’t really changed since watchOS 1. What’s changed is mostly that apps are faster, can do more work in the background, and have access to more features and frameworks.
In this first episode, I want to start with a bit of an overview of what pieces a watchOS app consists of, how they fit together and how it all changed since the first version. Like with my Dark Mode articles, what I’m trying to do here is collect the information I’ve found in docs like the App Programming Guide for watchOS, the official documentation site and in the WWDC videos I’ve watched, and organize it in a way that makes sense to me (and hopefully to others). I’ve found that very often related pieces of information are scattered through several talks, and it only starts making sense to me once I collect some notes while watching them and then rearrange everything, grouping things by topic – some things only start making sense in the context of some other information found in a different place.
This got a bit long, but bear with me, I’m expecting next episodes to be shorter and much more practical :)
The watchOS application
The first thing to note is that unlike macOS, iOS and tvOS, watchOS is not a completely independent platform: the Watch and the apps on it all rely to a bigger or smaller degree on the paired iPhone. A watch can work independently for some periods of time without the phone, especially if it’s an LTE variant, but it cannot completely exist without one. You need an iPhone to set it up, to update the OS, or to change some of the settings.
Watch apps use their iOS counterparts to store and sync data whenever possible. The iOS app is like mission control for a spaceship, controlling and guiding what it should do, sending the Watch app any information that it can usually get easier, and generally being something the Watch app can rely on being there when it needs it (except when the ship hides behind a moon and loses radio contact for a while…). Watch app storage also isn’t backed up at all to iCloud or local iTunes backup when you sync your phone, so you should not let it keep any precious information that can’t be recreated and isn’t synced to the iPhone; it should only be another view into some information that primarily exists elsewhere.
On disk, the watchOS app is bundled inside your iOS app bundle (they have the same bundle IDs). It’s downloaded to the iPhone together with the iOS app when you install it there, and then – automatically or manually – the Watch app can be copied over from the iPhone to the Watch.
What this means for you is that even if the watch app is your main focus, you will also need to build an iOS app to bundle it with. A watchOS app can’t exist by itself in a separate watchOS App Store, because there isn’t really such thing as a watchOS App Store – it’s just a filter in the iOS App Store that only shows apps that have a bundled watch app. And, as you can guess, in order to pass app review this iOS app needs to have at least some minimal functionality. (Though I’ve seen apps marketed specifically as watchOS apps get away with literally minimal functionality on iOS, e.g. Chirp.)
The Watch app and the extension
The most striking difference between how iOS and watchOS apps are built is that WatchKit apps are strictly divided into two very different parts. These two parts are packed into two different bundles and can’t directly communicate with each other at runtime.
The first part is called by Apple the Watch app – kind of confusingly, since what we normally understand as “app” is really both those parts taken together (they sometimes literally say that the watch app is a part of the watch app). I will often call it instead more explicitly “the app UI” or “the UI”.
This part consists only of your app’s storyboards and the assets you put in the bundle with them to be used in the UI. It does not include any code files. This part is managed by the system at runtime and you only have limited access to it.
The second part is called the WatchKit extension. This includes all of your watchOS code, your view controllers and models, everything your app does. It does not include any storyboards or XIBs, and in fact cannot create any pieces of UI itself. Creating your view hierarchies and layouts entirely in code, as some people do in iOS apps, is completely out of the question. Your code cannot actually add or remove a single thing from the UI, it can only change properties of existing UI elements – the list of views present in the view hierarchy and how they are arranged has to be decided up front and defined on the storyboard, and is set in stone when the app launches.
This is the single strangest thing in watchOS for me, and I expect this to be the biggest challenge and the thing that limits what your apps can do the most. It doesn’t mean you can’t modify anything in the UI apart from contents – you can actually achieve quite a lot by changing the
hidden properties of views or their exact positioning within groups (watch the “Layout and Animation Techniques for WatchKit” talk for some great examples), and you can even switch out whole screens inside container controllers. But it definitely limits the range of things that are possible at all.
On disk, the WatchKit extension bundle is packaged inside the UI bundle. This is kind of important, because it means that the UI can access any resources shipped both inside itself and the extension bundle, but the extension cannot access resources shipped inside the UI bundle. You need to keep that in mind when adding e.g. image assets, videos and other resources to the app.
The two parts also use separate data containers, so if the extension downloads some files or data from the internet or the iPhone, by default it puts them inside its own container, to which the UI has no access. In order to share files between the UI and the extension, you need to use App Groups and put the files in a shared data container.
So what’s changed?
What I’ve described above – about watchOS apps requiring a companion iOS app and being strictly divided into two separate parts – has worked more or less like this since watchOS 1.0 and it works like this now. That’s what I meant by saying that the changes turned out to be smaller than I imagined.
What’s changed between OS versions is where these parts are running at runtime:
- In watchOS 1, the app UI was running on the Watch, but the extension was running on the iPhone. This means that the way of communicating between the iOS app, the WatchKit extension and the UI and how the data is stored and moved has changed between watchOS 1 and 2, and that was probably the biggest problem during the migration. The extension could communicate very easily with the iOS app on the same device, but every communication between the extension (i.e. all of your code) and the UI had to go between devices, which made apps extremely slow.
- In watchOS 2, the WatchKit extension has been moved to the Watch (and this is what’s called a “native watch app”). This made apps run much faster, but it also made communication between the Watch app and the iOS app trickier – instead of just making local function calls to the other app and sharing data through a shared container, you now need to transfer all information manually. To solve this, the WatchConnectivity framework was introduced that lets you send data back and forth between the phone and the watch. (Another problem was that the extension now had access to a more limited set of SDKs on watchOS instead of all iOS ones, although a lot of the missing ones have been added to watchOS later.)
- In watchOS 4, the extension and the UI have been made to run inside a single process in memory. The slide from the WWDC talk with the moving boxes looked like the one during 1->2 migration, but in fact very little has changed this time from the developer’s perspective, and apps didn’t even need to be recompiled. The only effect is that apps now run even faster, but the two parts are still strictly isolated, so it didn’t make the development any easier than before or enable any new functionality.
More than one UI
iOS apps usually have one main point of entry. When people talk about an iOS app, they usually think of the icon that they see on their home screen and the full screen view that appears when they select it. There are of course various extensions that can let you access the app from different parts of the system – like share extensions or “today” widgets – but they’re generally considered only accessories to the main app, in which you spend most of the time.
With watchOS apps, things are very different. There is a main UI, but depending on your use case it might not be the most commonly used one.
The difference is that Apple Watch apps have completely different interaction patterns than iOS apps. On iOS, we open an app and then spend many minutes, possibly hours, scrolling the content (to the point that Apple had to add the Screen Time feature in iOS 12 to help us control ourselves).
On watchOS, the key word repeated by Apple over and over is glances or glanceable. The expected way of interacting with an app is: raise your wrist, look at the watch, make one or two taps (or even none at all), maybe scroll the crown a little bit, lower the wrist, and go on with your life. The average time spent on such action will be measured in seconds – in fact, it’s recommended that you try to target 2 seconds as the time in which the user can find the information they want (“glanceable”) or perform the action they need (“actionable”). No one will be holding their arm up for 15 minutes browsing Twitter on a 1.5" screen, because it’s just not comfortable.
And if you’ve ever used any watchOS app, you probably know that it can be tricky to find the information you want there. First, you have to find the app on the home screen, which can take forever on the hexagonal grid (the list view is much better IMHO). Then you have to wait for it to load, and the screen sometimes goes dark before that happens. And then search through the different screens in the app to find what you need.
Because of this, depending on the kind of app you build, it’s very much possible that your main UI will only be used occasionally. WatchKit apps can have a few other entry points which can be just as or even more important, which I’ll talk about below.
Notifications were present on iOS since basically forever, and watchOS doesn’t really provide anything beyond what we already know – it simply forwards all incoming notifications to the watch and shows them there (as long as it’s unlocked and you aren’t using the iPhone at the same time). It does that for all iOS apps – even those which don’t include a Watch app at all (which is the vast majority of them).
If you do provide a special notification interface for watchOS, you can show any custom non-interactive UI as the notification content and some action buttons below. Since watchOS 5, the UI can also be interactive (although it’s still just one simple screen).
However, because of the ease of access – you literally need less than half a second to look at your watch, compared to pulling your iPhone out of your pocket or purse – a lot of people will tell you that notifications are the main thing they use their Watch for (apart from just checking the time).
This will depend greatly on the kind of app you’re building, because for a lot of apps it makes no sense to have notifications (and it could even make the experience worse if they insisted on adding them). But if your app’s purpose is to notify users of some things that happen at irregular intervals, notifications could be the single most important piece of your app. One example could be the Reminders app – you can set reminders through Siri and get notified through notifications, and you might rarely need to open the actual app at all.
Glances / Dock
Since version 1, watchOS has included an interface kind of similar to the iOS app switcher, accessible by swiping up from the bottom edge. It showed a set of app “cards” side by side, scrolled horizontally. These cards were called “glances”, and they were meant to present some selected, most important pieces of information from the app in a way that lets you quickly scroll through the list, look at the card, read the information you need and leave. If you wanted to learn more or perform some actions, then you could tap a card and get redirected to the main app UI, but that shouldn’t be a requirement.
Glances were built from a completely separate scene on your storyboard and were set up separately from the main UI, using one of predefined templates. However, in watchOS 3 they were deprecated and replaced with a “dock”, accessed by pressing the long button on the side of the watch.
The dock works in a very similar way as glances, but the cards shown there are based on the actual UI of the main app (like on iOS). The way it’s done is that at various points in the app lifecycle the OS saves a “snapshot” of what can be seen on the currently visible view controller, and that snapshot is shown initially as a static image in the dock. When you stop scrolling the dock and settle on a selected app, the system starts waking up the app and a moment later the actual live view of the real app replaces the image (you can see that e.g. if you start a timer in the Timer app – it shows a static time at first, which starts animating once you scroll the app into view and wait a short moment).
(It was mentioned that the dock snapshot view does not have to be identical to what’s displayed in the app, and it could show some special, simplified view if it makes more sense, but it’s not clear to me how this should be done. The API doesn’t receive a reference to a screen or an image, it simply saves the current screen after you tell it you’ve finished updating it.)
In watchOS 4 the dock UI was replaced with a vertically-scrolling one. This made it more similar to the iOS version, since the cards overlap one another a bit, but at the same time it has arguably made it less “glanceable”. You can say it traded the ability to easily see all information on the cards for the ability to quickly find the app you want to switch to. Apple however still emphasizes how important it is that the information on the dock snapshots be kept up to date at all times.
“Complications” are a funny name Apple uses for all the widgets you can see on the watch face:
There are several different “families” of complications, built for different watch faces – circular or rectangular, small and large, and the Series 4 Watch even adds some curved ones that fit in the corners of the crazy new Infograph watch face. On the picture above, you can see complications from the “Modular Small” and “Modular Large” families.
What they (almost) all have in common is that they have an extremely small amount of space to present their information. They are also visible all the time (those that are active) and need to be always up to date.
As you can imagine, this couldn’t have been implemented by just letting apps run in the background whole day and giving them full access to their piece of the watch face, because this would have drained the battery too fast.
The way Apple has solved this is that you periodically provide a timeline of values to be shown on the complication in given time ranges, and you do this in advance. The system stores this data and then automatically switches to the correct state at a right moment. You also can’t show any arbitrary content on your complication – you have to choose one of the predefined templates for a given complication family, and then fill it with some data prepared in a way that potentially lets the system simplify it if necessary to fit it in the available space. Most complications are also monochrome – they are drawn with the same color selected by the user as everything else on the watch face.
This all makes it incredibly challenging to figure out how to do something useful with this tiny space with all those restrictions – but in a way constraints can also simplify your work, because you only have a limited number of options to choose from.
Apple has initially said that complications only make sense for some subset of apps, which is of course true – not every app will have some crucial piece of information it can show as a single number or one line of text. However, since watchOS 3 they started recommending that all apps actually implement a complication, even if it’s just a static launcher that gets you into the main app (a lot of built-in apps have followed this approach).
The reason is that if you have some app that you use often, a complication on the watch face really is the fastest way to open it – a single tap and you’re there. And it’s even more important since in watchOS 3 Apple has made it so that all apps that have an complication on the current watch face (and also all apps in the dock) are kept suspended in memory if possible the whole time, so they open much faster. So consider adding a complication as a launcher for your app, even if it doesn’t actually do anything else.
The last point of entry is Siri. I haven’t really looked into this part so far so I can’t tell you much, but as I understand, SiriKit has been available on watchOS since 3.2 and it has allowed voice access for some specific subset of apps like messaging, todo lists or taxi apps for a while. With Siri Shortcuts in iOS 12 and watchOS 5, this has now been expanded to a lot more possible use cases.
watchOS 5 also gives you access to the Siri watch face, where you can provide entries for the list visible on the watch face that either show some piece of information from your app, or act as shortcuts to quickly perform some kind of action:
Again, depending on the kind of app you’re building, this might be something you can completely ignore, or potentially the most often used view into your app. You decide.
With the overview part behind us, let’s start building something now :)
I’ve created a repo for the app on GitHub – for now it’s just an empty app from the template, but watch it if you’re interested. In the next episode, we’ll try to build some minimal viable version that shows some actual data.