WatchOS 4 : Getting started
This is an introductory tutorial for getting started with watchOS 4. The goal of this tutorial is to familiarize you with the important classes and workings of watchOS, how it interacts with its companion iOS app, and an overview of things you can do to leverage the technology stack. So let’s get started.
Terminologies:
- The watch app: This is the actual app that we would be creating.
For this tutorial, we create a simple counter app for watch, which shows a number on the watch face (let’s name it nCounter). We will use the watch crown to increment or decrement the count displayed. Further, we will see how to send this sample data to the companion iOS app, and consume it there.
See the expected UI for the watch app below:
2. Notifications: You can notify your users of certain useful events even when your app is not running through notifications. Much of the behavior for handling the notifications comes inherently from the watchOS, though you can still customize the experience to a certain extent.
3. Complications: As per apple docs,
Complications are small elements that appear on the watch face and provide quick access to frequently used data. Users can customize most watch faces and install the complications that they want to see.
So basically it’s the miniature display of your app on the watch-face, along with time and other complications allowed by the user.
Understanding the watch app folder structure:
Fire up your XCode, then File -> New -> Project -> WatchOS -> iOS App with WatchKit app. I named it FirstWatchApp. Here’s what the file structure looks like:
As we can see, it is structured as follows:
- The iPhone App (FirstWatchApp): Contains the files and resources needed for the phone application.
- The WatchKit App (FirstWatchApp WatchKit App): Contains the interface files and the resources needed for the Watch app. It represents the full-screen UI that you see when your app is launched from the watch home screen.
- The WatchKit extension (FirstWatchApp WatchKit extension): It contains the code for managing UI and responding to UI. XCode provides with the boilerplate code for implementing the initial interface (watch-equivalent of a view controller), notification, and complication. It also contains a sample .APNS push payload to test the notifications.
Also note, this project comes with a separate scheme for the phone app, watch app, watch notification, and watch complication.
You can run the code for WatchKit App schema, and it will show you an empty black screen on the watch simulator. Let’s add some elements there.
Designing the watch app UI
You can add elements to your watch app only from a pre-defined list of elements provided by Apple. These include tables, images, labels, action-sheets, etc.
For positioning the elements, we do not have constraints here. Instead, we rely on an element called group, which acts as a container and manages the layout of elements within itself. Essentially, the group defines the layout (vertical or horizontal), background color, and size, amongst other things.
For our nCounter watch app, let’s add a ‘Group’ to the Interface controller scene, and then let’s add two labels and one button within the group. Make sure you set the “layout” property of the group to vertical.
Next, adjust the alignment for the three labels. Make the horizontal and vertical alignment as (left, top) for Title label, (center, center) for Count label, and (center, bottom) for Reset button. Once your IB looks like the image above, add the IBOutlets for both the labels to InterfaceController.swift, and also an IBAction for the button.
In the InterfaceController.swift, add a function
and call it from the function awake(withContext context: Any?) and pass any string, say “21”, to make it work. Run the watch app again. You should see a static UI similar to what we intended at the beginning of this tutorial.
Taking a closer look 🕵️
So what are those classes in the WatchKit extension, and whats exactly happening here ?
Interface controller
Let’s start with the interface controller. This a a subclass of WKInterfaceController, which you can think of as equivalent of a UIViewController. Here, it is important to note the following from the apple docs:
Your interface controller code runs locally on the user’s Apple Watch but is separate from the interface that it manages. When you change the value of an interface object in your code, the system forwards the needed information to your Watch app, which makes the corresponding changes onscreen.
The elements (buttons, labels, groups, etc)
These are all concrete subclasses of WKInterfaceObject. They include WKInterfaceLabel, WKInterfaceButton, WKInterfaceTable, WKInterfaceImage, WKInterfaceSlider, WKInterfacePicker, and a few more. The WKInterfaceObject has methods which allow the elements to be customized to a certain extent, such as size, alignment, etc. You cannot define your custom subclasses for these elements, neither can you read the displayed value of the elements from the outlets that you create.
ExtensionDelegate
This is a class that conforms to the WKExtensionDelegate protocol. Consider this as the App-delegate equivalent of the watch app.
You can use this delegate to respond to life-cycle events of the watch app, such as the activation and deactivation of your app. Additionally, you can also use this delegate to respond to actionable notifications.
NotificationController, ComplicationController
Since this is a beginner tutorial, we won’t go into much details for these classes. Just know that we can create custom notification interfaces for our watch app by subclassing WKUserNotificationInterfaceController f. As for the ComplicationController, this is a class which conforms to the CLKComplicationDataSource protocol. It works in conjunction with the ClockKit to provide the appropriate entries on the watch face when your app is allowed as a complication.
Wearing the crown 👑
Now that you are familiarized with the file structure of the watch app, the static UI, and the classes and the delegates involved, let’s get our hands dirty with some code. Let’s see how you can update the count displayed when the user rotates the digital crown.
Go to InterfaceController.swift. Add a new private property here, called crownDelta. Add another property called count, which represents the count displayed on the watch app. Next, extent the InterfaceController.swift to conform to the WKCrownDelegate protocol, as shown in the code below:
Note that the WKInterfaceController has property crownSequencer, which represents the digital crown. We set its delegate to self, and call the focus() function to begin delivery of its events.
Now run your WatchKit app again. One the watch simulator, you would initially see the default value for count that you set in the Interface.storyboard. Once you start rotating the digital crown, you should see the value getting updated.
Also, in the IBAction for the Reset button, just set count = 0, and it will reset the count appropriately.
Socializing a bit 👨👩👧
So now that you have a nice interactive watch app, what if it feels lonely and needs to talk to someone ?
There are many ways, but to get things rolling, let’s consider a simple case where your watch app would like to inform its companion iOS app about its last count, and the phone app would then display it there.
Your watch app communicates with it’s counterpart iOS app through WCSession object. As the apple doc reads:
Your iOS app and watchOS app must both create and configure an instance of this class at some point during their execution. When both session objects are active, the two processes can communicate immediately by sending messages back and forth. When only one session is active, the active session may still send updates and transfer files, but those transfers happen opportunistically in the background
Let’s begin by importing WatchConnectivity framework into InterfaceController.swift and ViewController.swift. Setup the InterfaceController as follows:
No, replace the code in the ViewController.swift with the snippet below. Note that I have added a label in the Main.storyboard for this view controller, and connected an IBOutlet here.
We will update its value to reflect the count on the watch app.
Now build for your watch schema as well as the phone schema. Once you have both the apps running, try rotating the crown on the watch, and see the value on the phone catch up with the value on the watch. Viola !! We just created a simple connection between the phone and the watch app.
Here’s what the final product looks like:
I have hosted the final code on here.
Feel free to experiment and explore, ‘cos this is just the tip of the iceberg.
Happy Coding !!