In this watchOS 4 tutorial for complete beginners, you’ll learn how to create the user interface for a fictional airline called Air Aber.
By
Audrey Tam
.
Sign up/Sign in
With a
free
Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Create account
Already a member of Kodeco?
Sign in
Sign up/Sign in
With a
free
Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Create account
Already a member of Kodeco?
Sign in
Update Note
: This tutorial has been updated to Swift 4/watchOS 4 by Audrey Tam. The original tutorial was written by
Mic Pringle
.
In this watchOS 4 Tutorial, you’ll build a simple but fully functional watchOS 4 app. Specifically, you will work on a Watch app for a fictional airline called Air Aber.
In the process, you’ll learn:
How to add a watchOS 4 target to an iOS app.
How to share data across the two targets.
How to add a watchOS 4 interface controller to the Storyboard, and lay out the interface objects.
How to create the
WKInterfaceController
subclass, and wire everything up.
Let’s get started! ┗(°0°)┛
Getting Started
Start by downloading the
starter project
for this tutorial.
Open it in Xcode, and build and run. You should see a blank white screen:
There’s not much to this project as it stands: it includes a few helper files you’ll need, and not much else. You’ll address that now!
Adding the WatchKit App
Select
File\New\Target…
. In the dialog that appears, choose
watchOS\Application\WatchKit App
, then click
Next
:
In the following screen, set
Product Name
to
Watch
, make sure
Language
is set to
Swift
, and uncheck any checkboxes that are checked. Click
Finish
:
You’ll be asked if you want to activate the watch scheme, which you do, so make sure to choose
Activate
:
Congratulations, you’ve just created your first Watch app! It really is that easy.
You’ll notice this action actually created two targets, not one, and two corresponding groups in the Project navigator. This is because the code of a Watch app actually runs as an extension bundled within the Watch app, in much the same way Today extensions on iOS work.
Expand the
Watch
and
Watch Extension
groups in the Project navigator, and you’ll see that the storyboard is in the
Watch
group, and the classes created by the target template are in the
Watch Extension
group:
Here’s the pattern you’ll follow moving forward: any code you add must reside within the
Watch Extension group
, and be added to the
Watch Extension
target, whereas any assets or storyboards must be added to the
Watch
group.
A Little Housekeeping
Before continuing, you need to remove a couple of things added by the target template that you’re going to replace.
Right-click on
InterfaceController.swift
in the Project navigator, and choose
Delete
. When prompted, choose
Move to Trash
to make sure the file is actually removed from the project:
Next, open
Interface.storyboard
, select the only interface controller in there, and hit the
delete
key. This should leave you with an empty storyboard, or as I prefer to think of it, a blank canvas.
Sharing Data and Code
The starter project includes a JSON file containing all the Air Aber flight details, and a model class that represents that data. This is exactly the kind of thing that you should share amongst targets, since it’s highly likely the iOS app and the Watch app will use the same model class and data – you do remember
DRY
, right?
Expand the
Shared
group in the Project navigator, and select
Flights.json
. Next, find the
Target Membership
section in the
File inspector
, and check
Watch Extension
:
The file is now included in both the
AirAber
and
Watch Extension
targets.
Repeat the process for the other file in the
Shared
group,
Flight.swift
.
And with that done, you can finally begin building the flight details interface!
Building the Interface
Open
Watch\Interface.storyboard
, and drag an
interface controller
from the Object Library onto the storyboard canvas. With the interface controller selected, open the Attributes inspector, and set
Identifier
to
Flight
, and check
Is Initial Controller
. Uncheck
Activity Indicator On Load
:
Here, you’ve set the identifier so you can refer to the interface controller in code. Checking
Is Initial Controller
simply informs WatchKit this is the interface controller you want to display when the Watch app first launches. This interface doesn’t download any data, so it doesn’t need to display the activity indicator.
In order to simplify this tutorial, you will build your layout only for the 42mm watch. For your own apps, you’ll want to verify that they work properly on all watch sizes. At the bottom left of the storyboard pane, ensure that it says
View as: Apple Watch 42mm
.
Watch app layout is completely different from iOS layout. The first thing you’ll notice: you can’t move or resize UI objects by dragging them around in the interface controller. When you drag an object onto the controller, it slots in under the previous objects, and the screen fills up pretty fast. To organize objects side-by-side, you use
groups
, which are a lot like stack views in iOS and macOS.
So first, drag a
group
from the Object Library onto the interface controller:
Although it doesn’t look like much now, this group will eventually contain the Air Aber logo, flight number and route.
With the new group selected, head over to the Attributes inspector, and change
Insets
to
Custom
. Four text fields appear, where you can manually set the top, bottom, left and right insets of the group.
Change
Top
to
6
:
This simply gives the layout group a little extra padding at the top.
Next, drag an
image
into the group. If your group shrank in response to changing the top inset (
thanks Xcode!
), drag the image into the document outline instead, making sure it’s a child of the group, rather than a sibling:
Now you need an image to display. Download
this logo image
and drag it into your
Watch\Assets.xcassets
. This creates a new image set called
Logo
, with the actual image in the 2x slot:
You want to tint this image to Air Aber’s corporate color, so select the image, then in the Attributes inspector, set
Render As
to
Template Image
.
Re-open
Watch\Interface.storyboard
, and select the
image
. Using the Attributes inspector, make the following changes:
Set
Image
to
Logo
– if it doesn’t appear in the dropdown, simply type it in.
Set
Tint
to
#FA114F
(you can type this in the Color RGB Sliders panel).
Set
Width
to
Fixed
, with a value of
40
.
Set
Height
to
Fixed
, with a value of
40
.
The Attributes inspector should now look like the following:
Don’t worry if you can’t see the logo: it turns out Xcode doesn’t tint template images at design time! Trust me, it’ll be a vibrant pink when you build and run!
Next, drag another
group
into the existing group, making sure it appears to the right of the image, and use the Attributes inspector to change its
Layout
to
Vertical
. Also, change
Spacing
to
Custom\0
and
Width
to
Size to Fit Content
.
Next, drag two
labels
into the new group. Because you set layout to vertical, the labels appear one above the other:
Select the upper label, and use the Attributes inspector to set
Text
to
Flight 123
and
Text Color
to
#FA114F
(instead of typing this into the RGB panel again, you can select the pink color from
Recently Used Colors
in the Color menu).
Next, select the lower label, and set its
Text
to
MEL to SFO
. Your interface controller should now look like the following:
This text is simply placeholder text that’ll be replaced when you hook the interface up to its controller class.
Next, drag another
group
onto the interface controller, but this time make sure it’s a sibling of the very first group you added. If you can’t get the group positioned at the correct place in the hierarchy, use the document outline instead.
With this new group selected, set its
Layout
to
Vertical
and
Spacing
to
Custom\0
.
Next, drag three
labels
into this new group:
Check in the document outline to make sure all three labels are
inside
the group, not siblings of the group!
Select the top label, and use the Attributes inspector to change its
Text
to
AA123 Boards
.
Next, select the middle label, and change its
Text
to
15:06
. Next, change
Text Color
to
#FA114F
and
Font
to
System
, with a
Style
of
Regular
and a
Size
of
54
. Finally, change
Height
to
Fixed
, with a value of
44
.
Select the bottom label, and change its
Text
to
On time
and
Text Color
to
#04DE71
.
Your interface controller should now look like the following:
Now you only have to add one more group, before you create the outlets, and have this interface display some real data.
Drag a new
group
from the Object Library into the lower group, this time making sure it’s a child rather than a sibling, and that it’s positioned at the very bottom of the containing group. Next, add two
labels
to it. Your complete interface object hierarchy should now look like this:
Use the Attributes inspector to set
Text
to
Gate 1A
for the left label. For the right label, set
Text
to
Seat 64A
, and set
Horizontal Alignment
to
Right
.
The completed interface should now look like the following:
Congratulations, you’ve finished laying out your very first Watch app interface! Now it’s time to populate it with some real data, and get it up and running in the simulator.
Creating the Controller
Right-click on the
Watch Extension
group in the Project navigator, and choose
New File…
. In the dialog that appears, select
watchOS\Source\WatchKit Class
, and click
Next
. Name the new class
FlightInterfaceController
, making sure it’s subclassing
WKInterfaceController
, and
Language
is set to
Swift
:
Click
Next
, and then
Create
.
When the new file opens in the code editor, delete the three empty method stubs, so you’re left with only the import statements and the class definition.
Add the following outlets to the top of
FlightInterfaceController
:
@IBOutlet var flightLabel: WKInterfaceLabel!
@IBOutlet var routeLabel: WKInterfaceLabel!
@IBOutlet var boardingLabel: WKInterfaceLabel!
@IBOutlet var boardTimeLabel: WKInterfaceLabel!
@IBOutlet var statusLabel: WKInterfaceLabel!
@IBOutlet var gateLabel: WKInterfaceLabel!
@IBOutlet var seatLabel: WKInterfaceLabel!
Here, you’re simply adding an outlet for each of the labels you added to the interface earlier. You’ll hook them up in just a moment.
Next, add the following property and property observer below the outlets:
var flight: Flight? {
didSet {
guard let flight = flight else { return }
flightLabel.setText("Flight \(flight.shortNumber)")
routeLabel.setText(flight.route)
boardingLabel.setText("\(flight.number) Boards")
boardTimeLabel.setText(flight.boardsAt)
if flight.onSchedule {
statusLabel.setText("On Time")
} else {
statusLabel.setText("Delayed")
statusLabel.setTextColor(.red)
gateLabel.setText("Gate \(flight.gate)")
seatLabel.setText("Seat \(flight.seat)")
Here’s the play-by-play of what’s happening:
You declare an optional property of type
Flight
. This class is declared in
Flight.swift
, which is part of the shared code you added to the Watch Extension target earlier.
You add a property observer that is triggered whenever the property is set.
You make sure there’s an actual flight rather than
nil
in the optional property. You only want to proceed with configuring the labels when you know you have a valid instance of
Flight
.
You configure the labels using the relevant properties of
flight
.
If the flight is delayed, you change the text color of the label to red.
Now you need to set
flight
when the controller is first shown. Add the following below the declaration of
flight
:
override func awake(withContext context: Any?) {
super.awake(withContext: context)
flight = Flight.allFlights().first
In the next part of this series, you’ll change this implementation to use the context that’s passed to it, but for now, you simply load all the flights from the shared JSON file, then take the first one from the array.
Note
:
awake(withContext:)
is called after the controller is loaded from the storyboard, and all its outlets are set up, so it’s a great place to set
flight
.
Now, there’s one final step before you can build and run, and that’s to connect the outlets.
Connecting the Outlets
Open
Watch\Interface.storyboard
, and select the interface controller. Using the Identity inspector, set
Custom Class\Class
to
FlightInterfaceController
.
Next, use your favorite method to connect the outlets according to the list below:
flightLabel
: Flight 123
routeLabel
: MEL to SFO
boardingLabel
: AA123 Boards
boardTimeLabel
: 15:06
statusLabel
: On time
gateLabel
: Gate 1A
seatLabel
: Seat 64A
Before you hit run, there’s just one more thing to do. The sample app you’re building throughout this tutorial has been designed for the 42mm Apple Watch, so you need to make sure you have the correct watch simulator set up, otherwise some things may look a little off. For a real world app, you’d want to make sure your interfaces work equally well across both sizes of watch, but that’s outside the scope of this tutorial.
Open the Watch scheme menu, and select one of the 42mm simulators:
Build and run. Once the simulator has finished loading, you should see your elaborate layout, with the logo tinted Air Aber pink. The
Flight
object generates random values for boarding time and seat number, so you’ll see different values there:
Note
: If you receive an error message stating the installation failed, then you can either try again with Xcode, or manually install the app in the watch simulator. To do this, open the Watch app in the iOS simulator, tap on
AirAber
, and then flick
Show App on Apple Watch
to
On
. Once that’s done, jump back to the watch simulator, tap the
Digital Crown
to navigate to the home screen, and then tap the AirAber icon to launch the app.
Congratulations! You’ve now finished implementing your very first WatchKit interface, and got it up and running in the watch simulator using real data — nice work. :]
Where To Go From Here?
Here is the
finished example
from this tutorial series so far.
In this exercise, you’ve learned how to add a Watch app to an existing iOS app, how to create an interface controller, and lay out a pretty complex interface using nested groups, and how to tie the whole thing together using a
WKInterfaceController
subclass. So, where to next?
Part 2 of this tutorial series, of course! In
Part 2
, you’ll learn all about tables and navigation in WatchKit.
If you have any questions or comments on this tutorial, please join the forum discussion below! :]
If you enjoyed this tutorial series, you’d definitely enjoy our book
watchOS by Tutorials
.
The book goes into further detail on making watchOS apps and is written for intermediate iOS developers who already know the basics of iOS and Swift development but want to learn how to make Apple Watch apps for watchOS 4.
It’s been fully updated for Swift 4, watchOS 4 and Xcode 9 — get it on the
raywenderlich.com store
today!
Sign up/Sign in
With a
free
Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Create account
Already a member of Kodeco?
Sign in
Sign up/Sign in
With a
free
Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Create account
Already a member of Kodeco?
Sign in
Sign up/Sign in
With a
free
Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Create account
Already a member of Kodeco?
Sign in
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more
Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive
catalogue of 50+ books and 4,000+ videos.
The largest and most up-to-date collection of courses and books on iOS,
Swift, Android, Kotlin, Flutter, Dart, Server-Side Swift, Unity, and more!