Apple Watch, bileğindeki en güçlü bilgisayar. Sağlık takibi, bildirimler, hızlı etkileşimler... Ve SwiftUI sayesinde geliştirmesi hiç bu kadar kolay olmamıştı. watchOS 10 ile tamamen yenilenen tasarım dili, developer'lara yeni fırsatlar sunuyor.
İçindekiler
- watchOS Geliştirmeye Giriş
- Proje Yapısı ve Mimari
- watchOS 10 Yeni Tasarım Dili
- Complications ve WidgetKit
- HealthKit ile Sağlık Verisi
- Workout Tracking
- Watch Connectivity: iPhone ile İletişim
- Bildirimler ve Background Tasks
- Production Best Practices
watchOS Geliştirmeye Giriş {#watchos-giris}
watchOS uygulamaları tamamen SwiftUI ile yazılır. UIKit yok. watchOS 10 ile birlikte navigation yapısı tamamen değişti - artık vertical page tabanlı değil, NavigationStack tabanlı.
watchOS Versiyonu | Temel Değişiklik |
|---|---|
watchOS 7 | Bağımsız app desteği |
watchOS 8 | Always-on display |
watchOS 9 | Workout improvements |
watchOS 10 | **Tamamen yeni tasarım, WidgetKit** |
Dış Kaynaklar:
Proje Yapısı {#proje-yapisi}
swift
1// watchOS App Entry Point2@main3struct MyWatchApp: App {4 var body: some Scene {5 WindowGroup {6 NavigationStack {7 ContentView()8 }9 }10 }11}12 13struct ContentView: View {14 var body: some View {15 TabView {16 // watchOS 10: Vertical TabView17 SummaryView()18 ActivityView()19 SettingsView()20 }21 .tabViewStyle(.verticalPage)22 }23}watchOS 10 Tasarım Dili {#watchos-10}
swift
1struct SummaryView: View {2 @State private var heartRate = 723 4 var body: some View {5 // watchOS 10: Full-screen background6 ScrollView {7 VStack(spacing: 16) {8 // Gauge - watchOS'ta çok kullanılır9 Gauge(value: Double(heartRate), in: 40...200) {10 Text("BPM")11 } currentValueLabel: {12 Text("\(heartRate)")13 .font(.system(.title, design: .rounded))14 }15 .gaugeStyle(.accessoryCircular)16 .tint(heartRate > 120 ? .red : .green)17 18 // Otomatik tarih/saat widget19 Text(Date(), style: .time)20 .font(.title3)21 22 // Grid layout23 LazyVGrid(columns: [24 GridItem(.flexible()),25 GridItem(.flexible())26 ], spacing: 8) {27 MetricCard(title: "Adım", value: "8,432", icon: "figure.walk")28 MetricCard(title: "Kalori", value: "340", icon: "flame.fill")29 MetricCard(title: "Mesafe", value: "5.2 km", icon: "location.fill")30 MetricCard(title: "Aktif", value: "45 dk", icon: "timer")31 }32 }33 .padding()34 }35 .containerBackground(.blue.gradient, for: .navigation)36 }37}38 39struct MetricCard: View {40 let title: String41 let value: String42 let icon: String43 44 var body: some View {45 VStack(spacing: 4) {46 Image(systemName: icon)47 .font(.title3)48 Text(value)49 .font(.headline)50 Text(title)51 .font(.caption2)52 .foregroundStyle(.secondary)53 }54 .frame(maxWidth: .infinity)55 .padding(8)56 .background(.ultraThinMaterial)57 .clipShape(RoundedRectangle(cornerRadius: 10))58 }59}Complications ve WidgetKit {#complications}
watchOS 10 ile birlikte Complications artık WidgetKit tabanlı:
swift
1import WidgetKit2import SwiftUI3 4struct StepCountWidget: Widget {5 var body: some WidgetConfiguration {6 StaticConfiguration(7 kind: "StepCount",8 provider: StepCountProvider()9 ) { entry in10 StepCountWidgetView(entry: entry)11 }12 .configurationDisplayName("Adım Sayacı")13 .description("Günlük adım sayınız")14 .supportedFamilies([15 .accessoryCircular,16 .accessoryRectangular,17 .accessoryInline,18 .accessoryCorner19 ])20 }21}22 23struct StepCountWidgetView: View {24 let entry: StepCountEntry25 @Environment(\.widgetFamily) var family26 27 var body: some View {28 switch family {29 case .accessoryCircular:30 Gauge(value: Double(entry.steps), in: 0...10000) {31 Image(systemName: "figure.walk")32 } currentValueLabel: {33 Text("\(entry.steps)")34 }35 .gaugeStyle(.accessoryCircularCapacity)36 .tint(.green)37 38 case .accessoryRectangular:39 VStack(alignment: .leading) {40 Label("Adımlar", systemImage: "figure.walk")41 .font(.headline)42 Text("\(entry.steps) / 10,000")43 .font(.body)44 ProgressView(value: Double(entry.steps), total: 10000)45 .tint(.green)46 }47 48 default:49 Text("\(entry.steps)")50 }51 }52}HealthKit ile Sağlık Verisi {#healthkit}
swift
1import HealthKit2 3class HealthKitManager {4 let healthStore = HKHealthStore()5 6 func requestAuthorization() async throws {7 let readTypes: Set<HKObjectType> = [8 HKObjectType.quantityType(forIdentifier: .heartRate)!,9 HKObjectType.quantityType(forIdentifier: .stepCount)!,10 HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,11 HKObjectType.workoutType(),12 ]13 14 try await healthStore.requestAuthorization(toShare: [], read: readTypes)15 }16 17 func fetchTodaySteps() async throws -> Int {18 let stepsType = HKQuantityType(.stepCount)19 let today = Calendar.current.startOfDay(for: Date())20 let predicate = HKQuery.predicateForSamples(withStart: today, end: Date())21 22 let descriptor = HKStatisticsQueryDescriptor(23 predicate: HKSamplePredicate.quantitySample(type: stepsType, predicate: predicate),24 options: .cumulativeSum25 )26 27 let result = try await descriptor.result(for: healthStore)28 return Int(result?.sumQuantity()?.doubleValue(for: .count()) ?? 0)29 }30 31 // Real-time heart rate monitoring32 func startHeartRateMonitoring() -> AsyncStream<Double> {33 AsyncStream { continuation in34 let heartRateType = HKQuantityType(.heartRate)35 let query = HKAnchoredObjectQuery(36 type: heartRateType,37 predicate: nil,38 anchor: nil,39 limit: HKObjectQueryNoLimit40 ) { _, samples, _, _, _ in41 if let sample = samples?.last as? HKQuantitySample {42 let bpm = sample.quantity.doubleValue(for: .count().unitDivided(by: .minute()))43 continuation.yield(bpm)44 }45 }46 47 query.updateHandler = { _, samples, _, _, _ in48 if let sample = samples?.last as? HKQuantitySample {49 let bpm = sample.quantity.doubleValue(for: .count().unitDivided(by: .minute()))50 continuation.yield(bpm)51 }52 }53 54 healthStore.execute(query)55 }56 }57}Watch Connectivity {#connectivity}
swift
1import WatchConnectivity2 3class WatchConnectivityManager: NSObject, WCSessionDelegate {4 static let shared = WatchConnectivityManager()5 6 func activate() {7 if WCSession.isSupported() {8 let session = WCSession.default9 session.delegate = self10 session.activate()11 }12 }13 14 // iPhone'a mesaj gönder15 func sendMessage(_ message: [String: Any]) {16 guard WCSession.default.isReachable else {17 // iPhone erişilemez - background transfer kullan18 WCSession.default.transferUserInfo(message)19 return20 }21 22 WCSession.default.sendMessage(message) { reply in23 print("Reply: \(reply)")24 } errorHandler: { error in25 print("Error: \(error)")26 }27 }28 29 // iPhone'dan mesaj al30 func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {31 DispatchQueue.main.async {32 NotificationCenter.default.post(33 name: .receivedFromPhone,34 object: nil,35 userInfo: message36 )37 }38 }39 40 func session(_ session: WCSession, activationDidCompleteWith state: WCSessionActivationState, error: Error?) { }41}Production Best Practices {#best-practices}
🔑 Çıkarımlar
- Hızlı etkileşim - Watch'ta 5 saniyeden uzun işlem yapma
- Complications öncelikli - Kullanıcılar app'ten çok complication'ı görür
- HealthKit izinlerini açıkla - Privacy description zorunlu
- Background refresh akıllı kullan - Pil ömrü kritik
- WatchConnectivity fallback'li olsun - iPhone her zaman erişilemez
- Digital Crown gesture'larını destekle
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.

