Skip to content
All essays
iOSMarch 27, 202511 min

Swift WatchKit: Apple Watch Development

Create apps for Apple Watch with WatchKit. Complications, notifications, and watch-specific features.

Ü
Ümit Uz
Mobile & Full Stack Developer

WatchKit enables you to create apps for Apple Watch. Learn to build watchOS apps with complications and notifications.

WatchKit Setup

Add Watch Target

  1. 1File > New > Target
  2. 2Choose "watchOS App" target
  3. 3Configure watchOS deployment target

Basic Watch App

swift
import WatchKit import SwiftUI struct WatchAppView: View { var body: some View { VStack { Text("Hello, Watch!") .font(.title) Image(systemName: "heart.fill") .imageScale(.large) .foregroundColor(.red) } } }

Complications

Create Complication

swift
import ClockKit class ComplicationController: NSObject, CLKComplicationProvider { func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) { switch complication.family { case .modularSmall: let entry = createSmallEntry() handler(entry) case .modularLarge: let entry = createLargeEntry() handler(entry) case .utilitarianSmall: let entry = createUtilitarianEntry() handler(entry) default: handler(nil) } } func createSmallEntry() -> CLKComplicationTimelineEntry { let template = CLKComplicationTemplateModularSmall() let textProvider = CLKSimpleTextProvider(text: "Steps") template.body1TextProvider = textProvider return CLKComplicationTimelineEntry(date: Date(), template: template) } func createLargeEntry() -> CLKComplicationTimelineEntry { let template = CLKComplicationTemplateModularLarge() let header = CLKSimpleTextProvider(text: "Fitness") let body1 = CLKSimpleTextProvider(text: "10,000 steps") template.headerTextProvider = header template.body1TextProvider = body1 return CLKComplicationTimelineEntry(date: Date(), template: template) } func createUtilitarianEntry() -> CLKComplicationTimelineEntry { let template = CLKComplicationTemplateUtilitarianSmall() let textProvider = CLKSimpleTextProvider(text: "Watch App") template.bodyTextProvider = textProvider return CLKComplicationTimelineEntry(date: Date(), template: template) } }

Health Data

Watch Health Integration

swift
import HealthKit class WatchHealthManager: ObservableObject { private let healthStore = HKHealthStore() @Published var stepCount: Int = 0 @Published var activeEnergy: Double = 0 func queryHealthData() { guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else { return } let now = Date() let startOfDay = Calendar.current.startOfDay(for: now) let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate) let query = HKStatisticsQuery( quantityType: stepType, quantitySamplePredicate: predicate, options: .cumulativeSum ) { query, result, error in guard let result = result else { print("Error fetching steps: \(error)") return } let steps = result.sumQuantity()?.doubleValue(for: .count()) ?? 0 DispatchQueue.main.async { self.stepCount = Int(steps) } } healthStore.execute(query) } func queryHeartRate() { guard let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate) else { return } let now = Date() let startTime = Calendar.current.date(byAdding: .hour, value: -1, to: now)! let predicate = HKQuery.predicateForSamples(withStart: startTime, end: now, options: .strictStartDate) let query = HKSampleQuery( sampleType: heartRateType, predicate: predicate, limit: 1, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)] ) { query, samples, error in guard let sample = samples?.first as? HKQuantitySample else { return } let heartRate = sample.quantity.doubleValue(for: .count()) DispatchQueue.main.async { print("Heart Rate: \(heartRate) BPM") } } healthStore.execute(query) } }

Watch Connectivity

Phone Communication

swift
import WatchConnectivity class WatchConnectivityManager: NSObject, ObservableObject, WCSessionDelegate { private let session = WCSession.default @Published var isReachable = false @Published var isWatchAppInstalled = false override init() { super.init() session.delegate = self session.activateSession() } func session(_ session: WCSession, activationDidCompleteWith activationState state: WCSessionActivationState, error: Error?) { if let error = error { print("Watch activation error: \(error)") } else { print("Watch activation complete") } } func sessionReachabilityDidChange(_ session: WCSession) { isReachable = session.isReachable } func session(_ session: WCSession, didChangeCompanionAppInstalled installed: Bool) { isWatchAppInstalled = installed } func transferData(data: Data) { let transfer = WCSessionFileTransfer(fileNames: ["data.txt"]) transfer.transferFile(data, metadata: nil) } }

Watch Notifications

Custom Notifications

swift
import UserNotifications class WatchNotificationManager { func scheduleWatchNotification(title: String, body: String, delay: TimeInterval) { let content = UNMutableNotificationContent() content.title = title content.body = body content.sound = .default let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay, repeats: false) let request = UNNotificationRequest( identifier: UUID().uuidString, content: content, trigger: trigger ) UNUserNotificationCenter.current().add(request) { error in if let error = error { print("Error scheduling notification: \(error)") } } } func sendInteractiveNotification() { let content = UNMutableNotificationContent() content.title = "Quick Actions" content.body = "Choose an action" content.sound = .default content.categoryIdentifier = "interactive" let acceptAction = UNNotificationAction( identifier: "ACCEPT", title: "Accept", options: .foreground ) let declineAction = UNNotificationAction( identifier: "DECLINE", title: "Decline", options: .destructive ) let category = UNNotificationCategory( identifier: "interactive", actions: [acceptAction, declineAction], intentIdentifiers: [] ) UNUserNotificationCenter.current().setNotificationCategories([category]) let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest( identifier: UUID().uuidString, content: content, trigger: trigger ) UNUserNotificationCenter.current().add(request) } }

Digital Crown

Crown Input

swift
import Crown struct CrownControlView: View { @State private var scrollOffset: CGFloat = 0 var body: some View { ScrollView(offset: $scrollOffset) { VStack(spacing: 20) { ForEach(0..<100) { i in Text("Item \(i)") .font(.title) } } } .focusable(true) .digitalCrownRotation($scrollOffset) { value in // Handle crown rotation print("Crown rotation: \(value)") } } }

Force Touch

Haptic Feedback

swift
struct ForceTouchView: View { @State private var forcePressed = false var body: some View { VStack { Image(systemName: forcePressed ? "heart.fill" : "heart") .font(.largeTitle) .foregroundColor(forcePressed ? .red : .gray) .scaleEffect(forcePressed ? 1.2 : 1.0) } .gesture( DragGesture(minimumDistance: 0) .onChanged { value in // Detect force touch // (Implementation depends on watchOS version) } ) } }

SwiftUI for watchOS

Native Watch Layouts

swift
struct WatchContentView: View { var body: some View { ScrollView { VStack(spacing: 10) { Text("Features") .font(.headline) Button("Feature 1") { // Action } Button("Feature 2") { // Action } } } .navigationTitle("App") } }

Best Practices

  1. 1Simplicity: Keep watch apps simple
  2. 2Performance: Optimize for watch hardware
  3. 3Battery: Conserve battery life
  4. 4Sync: Use Watch Connectivity
  5. 5Complications: Provide useful complications
  6. 6Notifications: Use notifications effectively
  7. 7Crown: Leverage digital crown
  8. 8Testing: Test on real devices

WatchKit enables powerful Apple Watch experiences!

Next essay
Swift Vision Framework: Computer Vision