Building an Apple Watch app in SwiftUI

Apostolis Apo
4 min readDec 8, 2019

--

SwiftUI first saw the light this summer and we have been able to submit apps written in it since September. It was released to almost universal acclaim mostly for its potential and despite many missing APIs and the abysmal quality of the Xcode debugger.

My first thought when I saw how much easier and efficient it can be to develop an app in SwiftUI was to devote some weeks to build some simple app and if the results are satisfactory why not even release it (?)

However, it turned out to be much harder than I thought and almost after a week of fighting I realized I would either have to make serious compromises or consume more time to develop in SwiftUI than do it in bare Swift which negates a big part of why SwiftUI was designed…

So, I thought that until SwiftUI becomes a little more mature and its debugger a lot more helpful I could go for an easier task. It happens that my one and only app I sell on App Store, KnowTrail, has also an Apple Watch companion. Given that watchOS 6 supports stand-alone apps, what if I replicated the behaviour of the existing companion app in a new, stand-alone one written in SwiftUI?

WatchOS is much narrower and simpler in scope than iOS so even such an early version of SwiftUI should to be able to support my app without major compromises.

Also, the nature of WatchOS apps which avoid fancy and complicated, non-standard backgrounds, tables and animations should benefit extremely from SwiftUI’s declarative way of doing things. And it turned out to be exactly that. When you just want a table or a button with some standard, common-sense layout (e.g. button should be centered or the table cell’s components should spread out proportionately) SwiftUI was amazing.

When you decide to go against the usual and implement fancy stuff, this is where the difference in effort time between Swift and SwiftUI becomes smaller. But as mentioned before, in WatchOS you rarely have to do this…

To come back to my app, what I set out to do was build an app where the user could see some Wikipedia information related to their current location, read some nice summary about it in a dedicated view, get directions to it by passing information to Apple Maps and finally choose among 30 languages as the source of this Wikipedia information.

In the next section, I will go through some of the screens of the app and show some bits of code to demonstrate how I built them and what pitfalls I was forced to avoid.

The main screen is a simple view where we can choose between Nearby and Settings. In bare Swift + Storyboards this would mean outlets, actions or segues (layouting was admittedly already quite simple compared to iOS). In SwiftUI it is just the following:

Main View (Code)
Main View

This piece of code defines my main view and its navigational behaviour with the two next screens. Simple eh?

Going to the view where the user can select the Wikipedia language things are equally simple barring a quirk regarding touch detection for every list item. Inside the list I had first defined each item as an HStack which is the default way of grouping elements into user-facing entities. Since I wanted each item to be a text label along with an optional check mark when it was selected, I went with this bit of code:

Languages View (code)
Language View

onTapGesture is supposed to listen for touch events in the whole stack including the empty space. But soon I discovered that touches were only registered my finger touched the Text part. That was especially obvious when the language title was short. But all is not lost; I discovered that by using Button as list item as shown below I gained the desired (and reasonable) functionality. I am not sure if the behaviour of HStack is a bug or meant to be like this. What I know is that it is not intuitive…

As a final example of how simple Apple Watch views can be we can look at how WikiEntryView for each point of interest is constructed:

Wiki Entry view (code)
Wiki Entry view

We can observe how convenient it is to wrap the whole view in a ScrollView and how again we avoid using graphical tools when we expect from our components to take their place in the screen.

If you wonder abou the GeometryReader — frame bit it is a practice of me to use it even if it is needed and in most cases I use it so that I secure that the screen will take the most place available in the screen (so that it will be greedy if we use regexp terminology).

That is all! Please use the comments in case you other/ more specific questions about SwiftUI on Apple Watch or in general.

--

--

Apostolis Apo

Paid to develop for mobile but would still do it for free. Makes music for free but has always wanted to do it for a living.