WatchKit enables you to create apps for Apple Watch. Learn to build watchOS apps with complications and notifications.
WatchKit Setup
Add Watch Target
- 1File > New > Target
- 2Choose "watchOS App" target
- 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
- 1Simplicity: Keep watch apps simple
- 2Performance: Optimize for watch hardware
- 3Battery: Conserve battery life
- 4Sync: Use Watch Connectivity
- 5Complications: Provide useful complications
- 6Notifications: Use notifications effectively
- 7Crown: Leverage digital crown
- 8Testing: Test on real devices
WatchKit enables powerful Apple Watch experiences!