Tüm Yazılar
KategoriwatchOS
Okuma Süresi
16 dk okuma
Yayın Tarihi
...
Kelime Sayısı
2.803kelime

Kahveni hazırla - bu içerikli bir makale!

watchOS 11 yeni Workout API, custom complications, interactive widgets, Double Tap gesture ve Always-On display optimizasyonu — production Watch app geliştirme.

watchOS 11: Workout API, Complications ve Live Activities 2026 Derin Rehber

# watchOS 11: Workout API, Complications ve Live Activities 2026 Derin Rehber

Apple Watch platformu her geçen yıl daha güçlü bir geliştirici ekosistemi sunuyor. watchOS 11 ile birlikte gelen yeni API'ler, Watch uygulamalarını gerçek anlamda "companion" olmaktan çıkarıp bağımsız, güçlü sağlık ve fitness deneyimlerine dönüştürüyor. Bu rehberde, production ortamında çalışan bir Watch uygulaması geliştirirken karşılaştığım tüm detayları, kod örneklerini ve performans tuzaklarını paylaşıyorum.

💡 Pro Tip: watchOS 11 geliştirirken her zaman gerçek donanım üzerinde test edin. Simülatör, Always-On display davranışını, sensör verisini ve Double Tap gesture'ı tam olarak simüle edemiyor. Erken aşamada gerçek cihaz testine yatırım yapın.

İçindekiler

  • Workout API ve HKWorkoutBuilder Mimarisi
  • Custom Workout Types ve Metrics
  • Double Tap Gesture Entegrasyonu
  • Smart Stack Widget Algoritması
  • Live Activities on Watch
  • Complications: WidgetKit vs CLKComplicationTimelineEntry
  • Always-On Display Optimizasyonu
  • Background Tasks ve Limit Yönetimi
  • HealthKit Sample Batching
  • Performance ve Pil Optimizasyonu

1. Workout API ve HKWorkoutBuilder Mimarisi

watchOS 11, HKWorkoutBuilder API'sini köklü biçimde yeniledi. Artık workout streaming, anlık metrik güncellemeleri ve custom workout type tanımlamaları çok daha esnek. Temelden başlayalım.

swift
1import HealthKit
2import WorkoutKit
3 
4class WorkoutManager: NSObject, ObservableObject {
5 private let healthStore = HKHealthStore()
6 private var workoutBuilder: HKWorkoutBuilder?
7 private var workoutSession: HKWorkoutSession?
8 
9 @Published var heartRate: Double = 0
10 @Published var activeCalories: Double = 0
11 @Published var elapsedTime: TimeInterval = 0
12 @Published var workoutState: HKWorkoutSessionState = .notStarted
13 
14 // watchOS 11: Yeni streaming delegate
15 private var liveWorkoutBuilder: HKLiveWorkoutBuilder?
16 
17 func startWorkout(type: HKWorkoutActivityType) async throws {
18 let configuration = HKWorkoutConfiguration()
19 configuration.activityType = type
20 configuration.locationType = .outdoor
21 
22 // watchOS 11: Session ve builder'ı aynı anda oluştur
23 let session = try HKWorkoutSession(
24 healthStore: healthStore,
25 configuration: configuration
26 )
27 let builder = session.associatedWorkoutBuilder()
28 
29 // watchOS 11: Veri kaynakları
30 builder.dataSource = HKLiveWorkoutDataSource(
31 healthStore: healthStore,
32 workoutConfiguration: configuration
33 )
34 
35 self.workoutSession = session
36 self.liveWorkoutBuilder = builder as? HKLiveWorkoutBuilder
37 
38 session.delegate = self
39 builder.delegate = self
40 
41 // watchOS 11: startActivity ile başlat
42 let startDate = Date()
43 session.startActivity(with: startDate)
44 try await builder.beginCollection(at: startDate)
45 }
46 
47 func pauseWorkout() {
48 workoutSession?.pause()
49 }
50 
51 func resumeWorkout() {
52 workoutSession?.resume()
53 }
54 
55 func endWorkout() async throws {
56 guard let session = workoutSession,
57 let builder = liveWorkoutBuilder else { return }
58 
59 let endDate = Date()
60 session.end()
61 
62 // Builder'ı kapat ve workout'u kaydet
63 try await builder.endCollection(at: endDate)
64 let workout = try await builder.finishWorkout()
65 
66 print("Workout saved: \(workout.uuid)")
67 }
68}
69 
70// MARK: - HKWorkoutSessionDelegate
71extension WorkoutManager: HKWorkoutSessionDelegate {
72 func workoutSession(
73 _ workoutSession: HKWorkoutSession,
74 didChangeTo toState: HKWorkoutSessionState,
75 from fromState: HKWorkoutSessionState,
76 date: Date
77 ) {
78 DispatchQueue.main.async {
79 self.workoutState = toState
80 }
81 }
82 
83 func workoutSession(
84 _ workoutSession: HKWorkoutSession,
85 didFailWithError error: Error
86 ) {
87 print("Workout session error: \(error)")
88 }
89}
90 
91// MARK: - HKLiveWorkoutBuilderDelegate
92extension WorkoutManager: HKLiveWorkoutBuilderDelegate {
93 func workoutBuilder(
94 _ workoutBuilder: HKLiveWorkoutBuilder,
95 didCollectDataOf collectedTypes: Set<HKSampleType>
96 ) {
97 for type in collectedTypes {
98 guard let quantityType = type as? HKQuantityType else { continue }
99 
100 let statistics = workoutBuilder.statistics(for: quantityType)
101 
102 DispatchQueue.main.async {
103 self.updateMetrics(statistics: statistics, type: quantityType)
104 }
105 }
106 }
107 
108 private func updateMetrics(
109 statistics: HKStatistics?,
110 type: HKQuantityType
111 ) {
112 switch type {
113 case HKQuantityType(.heartRate):
114 let bpm = statistics?.mostRecentQuantity()?.doubleValue(
115 for: HKUnit.count().unitDivided(by: .minute())
116 ) ?? 0
117 heartRate = bpm
118 
119 case HKQuantityType(.activeEnergyBurned):
120 let calories = statistics?.sumQuantity()?.doubleValue(
121 for: .kilocalorie()
122 ) ?? 0
123 activeCalories = calories
124 
125 default:
126 break
127 }
128 }
129 
130 func workoutBuilderDidCollectEvent(
131 _ workoutBuilder: HKLiveWorkoutBuilder
132 ) {
133 // Workout event'lerini handle et (lap, pause, segment, etc.)
134 let events = workoutBuilder.workoutEvents
135 print("Total events: \(events.count)")
136 }
137}

Bu yapı, gerçek zamanlı metrik akışını sağlıyor. watchOS 11'de HKLiveWorkoutBuilder, önceki versiyonlara kıyasla çok daha az batarya tüketimiyle çalışıyor çünkü internal polling mekanizması yerine event-driven bir mimari kullanıyor.


2. Custom Workout Types ve Metrics

watchOS 11, önceden tanımlı workout türlerinin ötesinde custom metrik tanımlamaya imkân veriyor. Özellikle spor uygulamaları için bu kritik bir özellik.

swift
1import WorkoutKit
2 
3// Custom workout metriği tanımlama
4struct CustomWorkoutMetric {
5 let identifier: String
6 let unit: HKUnit
7 let displayName: String
8 
9 static let swimmingPace = CustomWorkoutMetric(
10 identifier: "com.app.swimmingPace",
11 unit: HKUnit.second().unitDivided(by: .meter()),
12 displayName: "Pace"
13 )
14 
15 static let powerOutput = CustomWorkoutMetric(
16 identifier: "com.app.powerOutput",
17 unit: .watt(),
18 displayName: "Power"
19 )
20}
21 
22// Custom workout configuration
23class AdvancedWorkoutConfigurator {
24 
25 func createCyclingWorkout() -> WorkoutPlan {
26 // watchOS 11: WorkoutPlan API
27 let warmup = WorkoutStep(
28 goal: .time(10, .minutes),
29 displayName: "Warm Up"
30 )
31 
32 let mainSet = WorkoutStep(
33 goal: .time(45, .minutes),
34 displayName: "Main Set",
35 alert: HeartRateZoneAlert(
36 target: .zone(.aerobic),
37 metric: .heartRate
38 )
39 )
40 
41 let cooldown = WorkoutStep(
42 goal: .time(5, .minutes),
43 displayName: "Cool Down"
44 )
45 
46 let composition = WorkoutComposition(
47 steps: [warmup, mainSet, cooldown]
48 )
49 
50 return WorkoutPlan(
51 composition: composition,
52 activity: .cycling,
53 location: .outdoor
54 )
55 }
56 
57 // Interval workout oluşturma
58 func createIntervalWorkout(
59 intervals: Int,
60 workDuration: TimeInterval,
61 restDuration: TimeInterval
62 ) -> WorkoutPlan {
63 var steps: [WorkoutStep] = []
64 
65 let warmup = WorkoutStep(
66 goal: .time(5, .minutes),
67 displayName: "Warm Up"
68 )
69 steps.append(warmup)
70 
71 for i in 1...intervals {
72 let workStep = WorkoutStep(
73 goal: .time(workDuration, .seconds),
74 displayName: "Interval \(i)",
75 alert: HeartRateZoneAlert(
76 target: .zone(.anaerobic),
77 metric: .heartRate
78 )
79 )
80 
81 let restStep = WorkoutStep(
82 goal: .time(restDuration, .seconds),
83 displayName: "Rest \(i)"
84 )
85 
86 steps.append(contentsOf: [workStep, restStep])
87 }
88 
89 let cooldown = WorkoutStep(
90 goal: .time(5, .minutes),
91 displayName: "Cool Down"
92 )
93 steps.append(cooldown)
94 
95 return WorkoutPlan(
96 composition: WorkoutComposition(steps: steps),
97 activity: .running,
98 location: .outdoor
99 )
100 }
101}

Custom workout metriklerini HealthKit'e kaydetmek için HKQuantityType ile custom identifier kullanmanız gerekiyor. Ancak dikkat: App Store review ekibi, kendi oluşturduğunuz metrik tiplerinin HealthKit guidelines'a uygun olduğunu doğruluyor.


3. Double Tap Gesture Entegrasyonu

Apple Watch Series 9 ve Ultra 2 ile gelen Double Tap gesture, watchOS 11'de gelişmiş bir API'ye kavuştu. Artık WKInterfaceHandGestureProvider kullanmak yerine daha temiz bir SwiftUI entegrasyonu mevcut.

swift
1import SwiftUI
2import WatchKit
3 
4struct WorkoutControlView: View {
5 @StateObject private var workoutManager = WorkoutManager()
6 @State private var showingControls = false
7 
8 var body: some View {
9 ZStack {
10 // Ana metrik görünümü
11 WorkoutMetricsView(manager: workoutManager)
12 
13 if showingControls {
14 WorkoutControlsOverlay(manager: workoutManager)
15 .transition(.opacity.combined(with: .scale))
16 }
17 }
18 // watchOS 11: Double Tap için yeni modifier
19 .handGestureShortcut(.primaryAction) {
20 withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
21 handleDoubleTap()
22 }
23 }
24 }
25 
26 private func handleDoubleTap() {
27 switch workoutManager.workoutState {
28 case .running:
29 workoutManager.pauseWorkout()
30 case .paused:
31 workoutManager.resumeWorkout()
32 case .notStarted:
33 showingControls.toggle()
34 default:
35 break
36 }
37 
38 // Haptic feedback
39 WKInterfaceDevice.current().play(.click)
40 }
41}
42 
43// watchOS 11: Gesture provider doğrudan SwiftUI entegrasyonu
44struct WorkoutMetricsView: View {
45 @ObservedObject var manager: WorkoutManager
46 
47 var body: some View {
48 VStack(spacing: 12) {
49 // Heart rate büyük gösterim
50 VStack(spacing: 4) {
51 Text("\(Int(manager.heartRate))")
52 .font(.system(size: 56, weight: .bold, design: .rounded))
53 .foregroundStyle(.red)
54 .contentTransition(.numericText())
55 .animation(.spring, value: manager.heartRate)
56 
57 Text("BPM")
58 .font(.caption)
59 .foregroundStyle(.secondary)
60 }
61 
62 // Kalori
63 HStack(spacing: 20) {
64 MetricItem(
65 value: "\(Int(manager.activeCalories))",
66 unit: "CAL",
67 color: .orange
68 )
69 
70 MetricItem(
71 value: formatTime(manager.elapsedTime),
72 unit: "TIME",
73 color: .cyan
74 )
75 }
76 }
77 .padding()
78 }
79 
80 private func formatTime(_ interval: TimeInterval) -> String {
81 let hours = Int(interval) / 3600
82 let minutes = (Int(interval) % 3600) / 60
83 let seconds = Int(interval) % 60
84 
85 if hours > 0 {
86 return String(format: "%d:%02d:%02d", hours, minutes, seconds)
87 }
88 return String(format: "%02d:%02d", minutes, seconds)
89 }
90}
91 
92struct MetricItem: View {
93 let value: String
94 let unit: String
95 let color: Color
96 
97 var body: some View {
98 VStack(spacing: 2) {
99 Text(value)
100 .font(.system(size: 22, weight: .semibold, design: .rounded))
101 .foregroundStyle(color)
102 .contentTransition(.numericText())
103 Text(unit)
104 .font(.caption2)
105 .foregroundStyle(.secondary)
106 }
107 }
108}

Double Tap, kullanıcı deneyiminde büyük bir fark yaratıyor. Özellikle koşu sırasında telefona veya saate dokunmadan workout'u pause/resume etmek çok değerli. Test ederken dikkat etmeniz gereken nokta: Double Tap gesture, sistem tarafından da kullanılıyor (notification dismiss gibi), bu yüzden gesture conflict yönetimi önemli.


4. Smart Stack Widget Algoritması

watchOS 10 ile gelen Smart Stack, watchOS 11'de daha akıllı hale geldi. Sistem, hangi widget'ı ne zaman öne çıkaracağını belirlemek için Siri Intelligence kullanıyor. Siz de uygulamanızın bu algoritmaya dahil olmasını sağlayabilirsiniz.

Smart Stack sıralamasını etkileyen faktörler:

Contextual relevance: Uygulamanız belirli bir aktiviteyle (sabah koşusu, akşam egzersizi) ilişkiliyse sistem bunu öğreniyor. Bu ilişkiyi AppIntent ile açıkça bildirmek performansı artırıyor.

Widget refresh frequency: Çok sık refresh, Smart Stack sıralamasını olumsuz etkiliyor. watchOS 11'de widget refresh budget system, widget'larınızın "değer üretip üretmediğini" takip ediyor.

Relevance provider: TimelineEntryRelevance struct'ını doğru kullanmak kritik.

swift
1import WidgetKit
2import SwiftUI
3 
4struct WorkoutWidgetEntry: TimelineEntry {
5 let date: Date
6 let heartRate: Int
7 let activeCalories: Int
8 let workoutState: String
9 let relevance: TimelineEntryRelevance?
10}
11 
12struct WorkoutWidgetProvider: TimelineProvider {
13 
14 func placeholder(in context: Context) -> WorkoutWidgetEntry {
15 WorkoutWidgetEntry(
16 date: Date(),
17 heartRate: 142,
18 activeCalories: 450,
19 workoutState: "Running",
20 relevance: nil
21 )
22 }
23 
24 func getSnapshot(
25 in context: Context,
26 completion: @escaping(WorkoutWidgetEntry) -> Void
27 ) {
28 let entry = fetchCurrentWorkoutData()
29 completion(entry)
30 }
31 
32 func getTimeline(
33 in context: Context,
34 completion: @escaping(Timeline<WorkoutWidgetEntry>) -> Void
35 ) {
36 var entries: [WorkoutWidgetEntry] = []
37 let currentDate = Date()
38 
39 // Aktif workout varsa yüksek relevance
40 let isWorkoutActive = WorkoutDataStore.shared.isWorkoutActive
41 
42 for minuteOffset in 0..<15 {
43 let entryDate = Calendar.current.date(
44 byAdding: .minute,
45 value: minuteOffset,
46 to: currentDate
47 )!
48 
49 // watchOS 11: Relevance skorunu dinamik ayarla
50 let relevanceScore: Float = isWorkoutActive ? 1.0 : 0.3
51 let duration: TimeInterval = isWorkoutActive ? 60 : 300
52 
53 let relevance = TimelineEntryRelevance(
54 score: relevanceScore,
55 duration: duration
56 )
57 
58 let entry = WorkoutWidgetEntry(
59 date: entryDate,
60 heartRate: WorkoutDataStore.shared.currentHeartRate,
61 activeCalories: WorkoutDataStore.shared.currentCalories,
62 workoutState: isWorkoutActive ? "Active" : "Ready",
63 relevance: relevance
64 )
65 entries.append(entry)
66 }
67 
68 // Workout aktifse sık refresh, değilse seyrek
69 let refreshPolicy: TimelineReloadPolicy = isWorkoutActive
70 ? .after(Date().addingTimeInterval(60))
71 : .after(Date().addingTimeInterval(900))
72 
73 let timeline = Timeline(entries: entries, policy: refreshPolicy)
74 completion(timeline)
75 }
76 
77 private func fetchCurrentWorkoutData() -> WorkoutWidgetEntry {
78 WorkoutWidgetEntry(
79 date: Date(),
80 heartRate: WorkoutDataStore.shared.currentHeartRate,
81 activeCalories: WorkoutDataStore.shared.currentCalories,
82 workoutState: WorkoutDataStore.shared.isWorkoutActive ? "Active" : "Ready",
83 relevance: TimelineEntryRelevance(score: 0.5, duration: 120)
84 )
85 }
86}

5. Live Activities on Watch

watchOS 11, iPhone'daki Live Activities'i Watch'a taşıdı. Bu özellik, uygulamanız arka planda veya kapalıyken bile dinamik bilgi göstermeyi sağlıyor.

Live Activities Watch entegrasyonu için ActivityKit kullanılıyor ancak Watch'a özgü boyut kısıtlamaları var. Watch için ayrı bir ActivityContent layout tasarlamanız gerekiyor.

Kritik nokta: Watch Live Activity, iPhone'dan bağımsız çalışmıyor. iPhone'da başlatılan bir Live Activity, Watch'ta otomatik görünüyor ancak Watch-only Live Activity başlatmak için iPhone uygulamasının da çalışıyor olması gerekiyor. Bu, background task bütçenizi etkiliyor.


6. Complications: WidgetKit vs CLKComplicationTimelineEntry

watchOS 9'dan itibaren WidgetKit tabanlı complications öneriliyor ancak bazı watch face'lerde hâlâ CLKComplication API gerekiyor. 2026 itibarıyla hangi yaklaşımı kullanacağınızı belirleyen tablo şu:

WidgetKit Complications (Tercih edilmeli):

  • Modular, Infograph, Infograph Modular, Graphic Corner, Graphic Bezel watch face'lerinde çalışıyor
  • SwiftUI ile tasarlanıyor, animasyon desteği var
  • Smart Stack ile entegre, relevance API'si mevcut
  • watchOS 9+ zorunlu

CLKComplication (Legacy):

  • Tüm watch face'lerde çalışıyor
  • Timeline-based refresh, predefined templates
  • watchOS 2'den beri mevcut, geriye dönük uyumluluk için gerekli

Production uygulamalar için her iki sistemi de desteklemek öneriliyor. WidgetKit complication'ı primary, CLK'yı fallback olarak kullanın.


7. Always-On Display Optimizasyonu

Apple Watch Series 4 ve üstü ile Ultra serisi, Always-On Display (AOD) destekliyor. watchOS 11'de AOD optimizasyonu için backgroundRefreshMode API'si kritik rol oynuyor.

AOD modunda dikkat edilmesi gerekenler:

  • Animasyonları durdur veya yavaşlat
  • Parlak renkler yerine düşük ışıklı paletler kullan
  • Hassas bilgileri gizle (privacy mode)
  • Güncelleme sıklığını azalt
swift
1import SwiftUI
2import WatchKit
3 
4struct WorkoutAODView: View {
5 @Environment(\.isLuminanceReduced) private var isLuminanceReduced
6 @ObservedObject var manager: WorkoutManager
7 
8 var body: some View {
9 ZStack {
10 // AOD arkaplanı her zaman siyah
11 Color.black.ignoresSafeArea()
12 
13 if isLuminanceReduced {
14 // Always-On mod: minimal görünüm
15 AODMinimalView(manager: manager)
16 } else {
17 // Normal mod: tam görünüm
18 WorkoutMetricsView(manager: manager)
19 }
20 }
21 // watchOS 11: AOD refresh rate kontrolü
22 .onChange(of: isLuminanceReduced) { _, reduced in
23 if reduced {
24 // AOD modunda yüksek frekans güncellemelerini durdur
25 manager.reduceUpdateFrequency()
26 } else {
27 // Normal moda dön
28 manager.restoreUpdateFrequency()
29 }
30 }
31 }
32}
33 
34struct AODMinimalView: View {
35 @ObservedObject var manager: WorkoutManager
36 
37 var body: some View {
38 VStack(spacing: 8) {
39 // Sadece kritik metrikler, düşük parlaklık
40 Text("\(Int(manager.heartRate))")
41 .font(.system(size: 48, weight: .bold, design: .rounded))
42 // AOD için beyaz değil, grimsI
43 .foregroundStyle(.white.opacity(0.85))
44 
45 Text("BPM")
46 .font(.caption)
47 .foregroundStyle(.white.opacity(0.5))
48 
49 Divider()
50 .overlay(.white.opacity(0.2))
51 
52 Text(formatElapsed(manager.elapsedTime))
53 .font(.system(size: 20, weight: .medium, design: .rounded))
54 .foregroundStyle(.white.opacity(0.7))
55 }
56 .padding()
57 }
58 
59 private func formatElapsed(_ interval: TimeInterval) -> String {
60 let minutes = Int(interval) / 60
61 let seconds = Int(interval) % 60
62 return String(format: "%02d:%02d", minutes, seconds)
63 }
64}
65 
66// WorkoutManager'a AOD desteği
67extension WorkoutManager {
68 private var normalUpdateInterval: TimeInterval { 1.0 }
69 private var aodUpdateInterval: TimeInterval { 5.0 }
70 
71 func reduceUpdateFrequency() {
72 // Timer'ı yavaşlat
73 updateTimer?.invalidate()
74 updateTimer = Timer.scheduledTimer(
75 withTimeInterval: aodUpdateInterval,
76 repeats: true
77 ) { _ in
78 Task { @MainActor in
79 // Minimal güncelleme
80 self.updateElapsedTime()
81 }
82 }
83 }
84 
85 func restoreUpdateFrequency() {
86 updateTimer?.invalidate()
87 updateTimer = Timer.scheduledTimer(
88 withTimeInterval: normalUpdateInterval,
89 repeats: true
90 ) { _ in
91 Task { @MainActor in
92 self.updateAllMetrics()
93 }
94 }
95 }
96}

AOD'de en sık yapılan hata, normal görünümdeki animasyonları ve renkleri olduğu gibi bırakmak. Bu hem pil ömrünü ciddi ölçüde etkiliyor hem de Apple'ın HIG kurallarına aykırı. Review sırasında reddedilme nedeni olabiliyor.


8. Background Tasks ve Limit Yönetimi

watchOS, background task'ları çok kısıtlı bir bütçeyle yönetiyor. watchOS 11'de bu bütçe biraz genişledi ancak hâlâ çok dikkatli kullanılması gerekiyor.

Background refresh budget: Sistem, günlük ne kadar background refresh hakkınız olduğuna kullanım pattern'lerinize göre karar veriyor. Kullanıcının uygulamanızı ne sıklıkla açtığı, hangi saatlerde aktif olduğu gibi faktörler bu bütçeyi etkiliyor.

WKBackgroundTask türleri:

  • WKApplicationRefreshBackgroundTask: UI güncellemeleri için
  • WKURLSessionRefreshBackgroundTask: Arka plan ağ istekleri için
  • WKWatchConnectivityRefreshBackgroundTask: iPhone ile senkronizasyon için
  • WKSnapshotRefreshBackgroundTask: Dock snapshot güncellemesi için

Her task için setTaskCompleted() çağırmak şart. Çağırmazsanız sistem uygulamanızın background task bütçesini sıfıra düşürebilir.


9. HealthKit Sample Batching

HealthKit'e sık aralıklarla tek tek sample kaydetmek hem performans hem de pil açısından verimsiz. Bunun yerine batch kayıt kullanın.

swift
1class HealthKitBatchManager {
2 private let healthStore = HKHealthStore()
3 private var pendingSamples: [HKSample] = []
4 private let batchSize = 50
5 private let flushInterval: TimeInterval = 30
6 private var flushTimer: Timer?
7 
8 init() {
9 startFlushTimer()
10 }
11 
12 func addHeartRateSample(_ bpm: Double, at date: Date) {
13 let heartRateType = HKQuantityType(.heartRate)
14 let unit = HKUnit.count().unitDivided(by: .minute())
15 let quantity = HKQuantity(unit: unit, doubleValue: bpm)
16 
17 let sample = HKQuantitySample(
18 type: heartRateType,
19 quantity: quantity,
20 start: date,
21 end: date,
22 metadata: [
23 HKMetadataKeyWasUserEntered: false,
24 HKMetadataKeyDeviceManufacturerName: "Apple"
25 ]
26 )
27 
28 pendingSamples.append(sample)
29 
30 if pendingSamples.count >= batchSize {
31 Task {
32 await flushSamples()
33 }
34 }
35 }
36 
37 func addCalorieSample(_ calories: Double, start: Date, end: Date) {
38 let calorieType = HKQuantityType(.activeEnergyBurned)
39 let quantity = HKQuantity(unit: .kilocalorie(), doubleValue: calories)
40 
41 let sample = HKQuantitySample(
42 type: calorieType,
43 quantity: quantity,
44 start: start,
45 end: end
46 )
47 
48 pendingSamples.append(sample)
49 }
50 
51 private func startFlushTimer() {
52 flushTimer = Timer.scheduledTimer(
53 withTimeInterval: flushInterval,
54 repeats: true
55 ) { _ in
56 Task {
57 await self.flushSamples()
58 }
59 }
60 }
61 
62 func flushSamples() async {
63 guard !pendingSamples.isEmpty else { return }
64 
65 let samplesToSave = pendingSamples
66 pendingSamples.removeAll()
67 
68 do {
69 try await healthStore.save(samplesToSave)
70 print("Saved \(samplesToSave.count) samples to HealthKit")
71 } catch {
72 // Başarısız olursa tekrar kuyruğa ekle
73 pendingSamples.insert(contentsOf: samplesToSave, at: 0)
74 print("HealthKit save failed: \(error)")
75 }
76 }
77 
78 deinit {
79 flushTimer?.invalidate()
80 // Son samples"i senkron flush et
81 Task {
82 await flushSamples()
83 }
84 }
85}

Bu batch manager yaklaşımı, HealthKit write işlemlerini ciddi ölçüde optimize ediyor. Production'da 30 saniyelik interval, çoğu kullanım senaryosu için ideal denge noktası.


10. Performance ve Pil Optimizasyonu

watchOS uygulamalarında pil ve performans optimizasyonu, App Store başarısının kritik bir parçası. Kullanıcı review'larında "pil tüketiyor" şikayeti, uygulamanın kötü rating almasının başlıca nedeni.

Temel kurallar:

  • WKExtendedRuntimeSession sadece gerçekten ihtiyaç duyulan durumlarda kullanın
  • Core Location updates'i mümkün olan en düşük accuracy ile isteyin
  • HKAnchoredObjectQuery yerine workout sırasında builder'ın sağladığı dataSource'u tercih edin
  • View güncellemelerini @MainActor ile UI thread'e sınırlayın
  • Gereksiz @Published kullanımından kaçının, sadece gerçekten UI değişikliğine yol açan state'leri publish edin

watchOS 11 spesifik: Yeni backgroundRefreshMode: .ultraLowPower modu, Always-On display sırasında arka plan hesaplamalarını %70'e kadar azaltabiliyor. Workout uygulamaları için bu mod varsayılan tercih olmalı.


ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

Simülatör sınırlamaları için geliştirme sırasında kullanabileceğiniz pratik yöntemler: - **Heart rate simülasyonu:** `HKWorkoutBuilder` delegate metodlarını mock data ile manuel tetikleyin - **Double Tap:** Simülatörde çalışmaz; UI test target icinde gesture recognizer doğrudan tetikleyin - **AOD testi:** `isLuminanceReduced` için SwiftUI preview'de `.isLuminanceReduced` environment değerini `true` olarak set edin

Sonuç

watchOS 11, Apple Watch platformunu gerçek anlamda bağımsız ve güçlü bir geliştirici ekosistemi haline getiriyor. Workout API'sinin yenilenmesi, Double Tap'in daha iyi entegrasyonu ve AOD optimizasyon araçları, 2026 itibarıyla Watch-first uygulamalar geliştirmeyi mümkün kılıyor.

En kritik ders: watchOS geliştirme, iOS geliştirmenin küçültülmüş versiyonu değil. Pil, background budget, sensor access gibi kısıtlamalar tamamen farklı bir mimari yaklaşım gerektiriyor. Bu kısıtları tasarımın merkezine koyarak başladığınızda, hem daha iyi uygulamalar üretiyorsunuz hem de App Store review süreçleri çok daha sorunsuz geçiyor.


İlgili Yazılar

Kaynaklar

Etiketler

#watchOS 11#Apple Watch#Workout API#Complications#WidgetKit#HealthKit#2026
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz