Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
191: Building a Simple watchOS App
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
@Statevariable calledgoalAmountand set it to 64. - Add a
Textview below the current water amount that displays "Goal: 64 oz". - Implement a conditional color change: the current water amount text should turn from
.blueto.greenonce thewaterAmountis greater than or equal to thegoalAmount. - Ensure all these elements remain inside the
ScrollViewto prevent clipping on smaller watch faces.
There are no comments for now.