Live Activities iOS 16.1 ile production’a hazır oldu. Uygulamanızın anlık bilgisini kilit ekranında ve Dynamic Island’da göstermek için. Spor maçı skoru, Uber sürücü konumu, yemek siparişi durumu, timer gibi şeyler için ideal.
2 projede Live Activities implement ettim: bir fitness app’inde antrenman süresi takibi, bir teslimat app’inde kurye konumu. Bu yazıda öğrendiklerim.
Live Activity nedir, ne değildir
Live Activity:
- Kilit ekranında veya Dynamic Island’da görünür
- Sürekli güncellenir (push notification ile veya local’den)
- Maksimum 8 saat açık kalır (ya da 12 saat idle)
- Uygulamanız aktif olmak zorunda değil
Ne değil:
- Widget değil (widget static + timeline-based, Live Activity dynamic + event-driven)
- Persistent notification değil (hem görünüm hem davranış farklı)
- Arka plan iş yapan bir mekanizma değil (background task ayrı)
ActivityKit framework’ü
SwiftUI tabanlı, deklaratif bir framework. 4 ana bölüm:
- ActivityAttributes: activity’nin şeklini tanımlayan struct (statik ve dinamik state dahil)
- ActivityKit.Activity: runtime’da activity instance’ı yönetir (start, update, end)
- WidgetConfiguration (ActivityConfiguration): activity UI’ını tanımlar
- Dynamic Island: expanded, compact leading/trailing, minimal view’ları ayrı olarak tanımlanır
Basit örnek:
struct WorkoutAttributes: ActivityAttributes {
struct ContentState: Codable, Hashable {
var duration: TimeInterval
var caloriesBurned: Int
}
var workoutType: String
}Başlatma: uygulamanın tetiklediği an
Live Activity’yi siz kullanıcıyı arayıp başlatamazsınız. Kullanıcı uygulamanızı açıp bir aksiyon yapmalı (“antrenmanı başlat”, “siparişi onayla”).
let attributes = WorkoutAttributes(workoutType: "Koşu")
let state = WorkoutAttributes.ContentState(duration: 0, caloriesBurned: 0)
do {
let activity = try Activity<WorkoutAttributes>.request(
attributes: attributes,
content: .init(state: state, staleDate: nil),
pushType: .token
)
print("Activity started: \(activity.id)")
} catch {
print("Failed: \(error)")
}pushType: .token diyorsanız activity’nin remote push update’lerini alacağı anlamı. Push notification ile update için APNS token’ını toplamanız gerekiyor.
Güncelleme: 3 farklı yol
1. Local update: uygulamanız açıkken direct Activity.update() çağırırsınız.
await activity.update(using: newState)Kullanıcı app’in içindeyken uygulanır. App suspended olunca local update olmaz.
2. Push notification: APNS üzerinden Live Activity’ye özel payload gönderirsiniz. Apple’ın ayrı bir APNS endpoint’i var: liveactivity-sandbox.push.apple.com.
Payload:
{
"aps": {
"timestamp": 1234567890,
"event": "update",
"content-state": {
"duration": 1200,
"caloriesBurned": 150
}
}
}Bu yöntem en güçlüsü ama backend iş gerektiriyor. APNS p12 cert veya token-based auth, apns-push-type: liveactivity header, apns-topic: com.example.app.push-type.liveactivity topic.
3. Background task: uygulamanın periyodik update’i. BGTaskScheduler veya location-based background fetch. Sınırlı rate.
Dynamic Island UI tasarımı
Dynamic Island 3 state’te görünür:
- Minimal: sadece sol veya sağda icon. Başka bir Live Activity varken
- Compact: leading + trailing kısa içerik
- Expanded: long press ile genişliyor, daha fazla bilgi
Her state ayrı ayrı tasarlanmalı:
DynamicIsland {
expandedRegion(.leading) { ... }
expandedRegion(.trailing) { ... }
expandedRegion(.center) { ... }
expandedRegion(.bottom) { ... }
} compactLeading: {
...
} compactTrailing: {
...
} minimal: {
...
}Compact için tasarım disiplini şart: 15x15pt veya en fazla 20x20pt alanda bilgiyi vermek. İkon + 1-2 karakter genelde ideal. Cursor ile Dynamic Island’a geçiş animasyonları sistem tarafından yapılır.
Frekans ve update limit’leri
Push notification ile update’in rate limit’i var. Apple documentation çok net söylemiyor ama production tecrübem:
- Saniyede 1’den sık update etmeyin
- 4-8 dakikada en az 1 update yoksa sistem “stale” işaretler
- Kritik state change’de update, atomic değişimde değil
Teslimat uygulamasında GPS her 5 saniyede update’i Live Activity’ye push etmek overkill oldu, battery drain yarattı. 30 saniyede 1 update + mesafe değişimi belirli eşiği geçince update yapısına geçtik. Battery impact %80 azaldı.
End: activity’yi bitirme
Activity bittiğinde explicitly end etmeniz gerekiyor, yoksa 8 saat maksimum süre dolana kadar kilit ekranında kalır.
await activity.end(
using: finalState,
dismissalPolicy: .after(.now + 3600) // 1 saat sonra dismiss
)dismissalPolicy seçenekleri:
– .default: sistem karar verir
– .immediate: hemen kaldır
– .after(date): belirli tarihte
Teslimat uygulamasında sipariş teslim olduktan sonra “Teslim edildi” state’i 10 dakika görünsün istedik, sonra kalksın. .after(.now + 600) ile çözdük.
Hata durumları ve fallback
Kullanıcı Settings’ten Live Activity izni verebilir veya reddedebilir. ActivityAuthorizationInfo().areActivitiesEnabled ile kontrol etmelisiniz. İzin yoksa kullanıcıya Settings yönlendirmesi veren bir fallback UI iyi pattern.
Token’ı APNS’e kaydettikten sonra Apple’dan invalid token hatası alabilirsiniz. Activity ended olmuş ama token backend’de duruyor olabilir. Her update deneme’sinde 410 Gone dönerse token’ı temizleyin.
Test etmenin zorluğu
Simulator Live Activity göstermiyor. Dynamic Island için iPhone 14 Pro veya yeni gerekiyor. Uzaktan push test etmek için APNS cert + push payload + real device + aktif Live Activity gerekli. Test döngüsü zahmetli.
Xcode 15+’dan Live Activity debug tooling biraz iyileşti ama yine de real device test paradigmasından kaçış yok.
Kullanıcı memnuniyeti etkisi
İki projede de Live Activity eklenince:
- App open rate’i arttı (kullanıcı kilit ekranından durumunu görüyor, app’e girmeden)
- Yalnız bu değil, kilit ekranına bakıp doğrulama yapıyor, ek güven hissediyor
- App Store review’larında ‘Live Activity çok kullanışlı’ yorumları geldi
Live Activity ürününüzün ‘live status’ gösteren bir aksiyonu varsa yüksek ROI getiriyor. Yoksa zorlamaya gerek yok.