Skip to Content
Course content

191: Building a Simple watchOS App

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've mastered the basics of SwiftUI on iOS, but watchOS is a different beast entirely. The biggest constraint isn't the code—it's the screen. When you're designing for the wrist, you have to be ruthless about what stays and what goes. If a user has to spend more than five seconds looking at your app, you've probably failed the "glanceable" test.

To get a feel for this, let's build a simple Hydration Tracker. The goal is simple: a big number showing how many ounces of water the user has drank, and a button to add more. Nothing fancy, just something functional.

Setting up the Watch Target

First, you'll need to add a watchOS target to your project. In Xcode, go to File > New > Target and select "Watch App". I usually recommend the "Watch-only App" option if you're just practicing, as it keeps the project structure cleaner by removing the iOS companion app overhead.

Once that's set, we'll head straight into the ContentView.swift. I want the interface to be centered and bold. On a watch, contrast is your best friend.

struct ContentView: View {
    @State private var waterAmount: Int = 0
    
    var body: some View {
        VStack {
            Text("Water Intake")
                .font(.headline)
            
            Text("\(waterAmount) oz")
                .font(.system(size: 40, weight: .bold))
                .foregroundColor(.blue)
            
            Button(action: {
                waterAmount += 8
            }) {
                Label("Add 8oz", systemImage: "drop.fill")
            }
            .buttonStyle(.borderedProminent)
            .tint(.blue)
            
            Button("Reset") {
                waterAmount = 0
            }
            .buttonStyle(.bordered)
        }
    }
}

Fixing the Layout Overflow

Now, here is where I usually trip up when I first jump back into watchOS. I ran this code on the 40mm SE simulator, and I immediately noticed that the "Reset" button was getting clipped at the bottom. I had just piled everything into a VStack and assumed it would fit because it looked fine on my larger 45mm Ultra simulator.

The mistake was ignoring the variety of screen sizes. You can't just assume a vertical stack will fit. If you add even one more piece of text or a slightly larger font, the UI breaks. In a real-world app, this is a "crash" of the user experience.

The fix is simple: wrap the content in a ScrollView. This ensures that regardless of the watch model, the user can at least reach the buttons.

var body: some View {
    ScrollView {
        VStack(spacing: 12) {
            Text("Water Intake")
                .font(.headline)
            
            Text("\(waterAmount) oz")
                .font(.system(size: 40, weight: .bold))
                .foregroundColor(.blue)
            
            Button(action: {
                waterAmount += 8
            }) {
                Label("Add 8oz", systemImage: "drop.fill")
            }
            .buttonStyle(.borderedProminent)
            .tint(.blue)
            
            Button("Reset") {
                waterAmount = 0
            }
            .buttonStyle(.bordered)
        }
        .frame(maxWidth: .infinity)
    }
}

Polishing the Interaction

One thing I've learned from spending too much time in the watchOS HIG (Human Interface Guidelines) is that buttons should feel substantial. I used .buttonStyle(.borderedProminent) for the primary action because it fills the width of the screen by default on watchOS, making it much easier to hit with a thumb while walking.

I also added a bit of spacing to the VStack. When elements are too crammed together, it feels claustrophobic on that tiny screen. A little breathing room goes a long way toward making the app feel native rather than like a shrunk-down iPhone app.




📋 Practical Task

Adding a Daily Goal Indicator to the Hydration Tracker

Modify the Hydration Tracker app to include a daily goal feature. You will need to:

  • Create a new @State variable called goalAmount and set it to 64.
  • Add a Text view below the current water amount that displays "Goal: 64 oz".
  • Implement a conditional color change: the current water amount text should turn from .blue to .green once the waterAmount is greater than or equal to the goalAmount.
  • Ensure all these elements remain inside the ScrollView to prevent clipping on smaller watch faces.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.