Live Activities w iOS 26: kompletny przewodnik po Dynamic Island i ActivityKit
Live Activities w iOS 26 dają aplikacji żywą kartę na ekranie blokady i w Dynamic Island. Przewodnik pokazuje ActivityKit, Push-to-Start, interaktywne App Intents i Liquid Glass z pełnym kodem Swift 6.2 dla Xcode 26.3.
Live Activities w iOS 26 to mechanizm ActivityKit, który pozwala aplikacji pokazywać aktualizowane na żywo informacje na ekranie blokady, w Dynamic Island i na zegarku przez StandBy oraz Smart Stack. W tym przewodniku przeprowadzę cię przez pełny pipeline, od konfiguracji Widget Extension, przez ActivityAttributes i Push-to-Start, aż po nowości iOS 26: aktualizacje przez ActivityKit.PushType.token, interaktywne przyciski oparte o App Intents oraz integrację z Liquid Glass. Wszystkie przykłady działają z Xcode 26.3, Swift 6.2 i SDK iOS 26. Szczerze mówiąc, kilka z tych pułapek złapałem na własnej skórze, gdy wdrażałem śledzenie dostawy w produkcyjnej aplikacji food-delivery.
Live Activities wymagają Widget Extension z typem ActivityConfiguration, struktury implementującej ActivityAttributes oraz uprawnienia NSSupportsLiveActivities w Info.plist.
W iOS 26 maksymalny czas trwania aktywności to 12 godzin w trybie aktywnym i 8 godzin w stale-state, a frequentUpdates pozwala na ~120 push-update'ów dziennie zamiast dotychczasowych 50.
Push-to-Start (od iOS 17.2) umożliwia uruchamianie Live Activities zdalnie. W iOS 26 ten flow działa też dla widgetów Smart Stack na Apple Watch.
Interaktywne przyciski w Dynamic Island korzystają z App Intents z openAppWhenRun = false, dzięki czemu akcja wykonuje się bez wybudzania aplikacji.
Dynamic Island ma trzy stany: compactLeading/compactTrailing, minimal oraz expanded. Każdy trzeba zaprojektować, bo system wybiera prezentację na podstawie kontekstu.
Liquid Glass w iOS 26 wymaga użycia .glassBackgroundEffect() na komponentach Dynamic Island, by widok komponował się z systemowym tłem.
Czym są Live Activities w iOS 26?
Live Activities to lekkie widoki SwiftUI, które aplikacja publikuje przez framework ActivityKit, by pokazywać użytkownikowi stan trwającego zadania w czasie rzeczywistym. Pomyśl: wynik meczu, status dostawy jedzenia, krok treningu czy postęp odprawy lotniska. W odróżnieniu od zwykłych widgetów, Live Activity ma jasno określony cykl życia. Jest uruchamiana, aktualizowana wielokrotnie i kończona, a system gwarantuje jej miejsce na ekranie blokady oraz w Dynamic Island na urządzeniach iPhone 14 Pro i nowszych.
W iOS 26 Apple znacząco rozszerzyło ActivityKit. Maksymalny czas życia aktywności wzrósł z 8 do 12 godzin w trybie aktywnym, dodano ActivityContent<State> z wbudowanymi metadanymi staleDate i relevanceScore, a powiadomienia push uzyskały nowy typ frequentUpdates, który podwaja dzienny limit aktualizacji. Pojawił się też publiczny API do uruchamiania aktywności na watchOS 26 przez Smart Stack. Live Activity uruchomiona na iPhonie automatycznie pojawia się jako karta na zegarku, jeśli zadeklarujesz supportedFamilies = [.accessoryRectangular, .accessoryInline].
Z perspektywy użytkownika to po prostu „żywa karta", ale z perspektywy dewelopera Live Activity to Widget Extension działający w odrębnym procesie z limitem pamięci 30 MB. Cały stan, który chcesz pokazywać, musi przechodzić przez serializowalną strukturę ContentState. To zmienia sposób projektowania danych. Zamiast przekazywać model domenowy, projektujesz minimalny snapshot wystarczający do renderu. (Pierwszy raz mnie to zaskoczyło, gdy próbowałem wepchnąć całą encję zamówienia w state.)
Konfiguracja Widget Extension i ActivityAttributes
Pierwszy krok to dodanie do projektu nowego targetu Widget Extension z zaznaczoną opcją „Include Live Activity". Xcode wygeneruje szkielet z ActivityConfiguration, ale zwykle trzeba go od razu rozdzielić. Atrybuty trzymaj w pliku współdzielonym między aplikacją a rozszerzeniem, bo będziesz ich używać po obu stronach. Dodaj wspólny target membership do pliku z ActivityAttributes i pamiętaj o dodaniu klucza NSSupportsLiveActivities = YES do Info.plist aplikacji głównej.
Struktura ActivityAttributes opisuje dwa rodzaje danych. Attributes to niezmienne dane ustawiane przy starcie (numer zamówienia, ID meczu), a wewnętrzna struktura ContentState to to, co zmienia się podczas trwania aktywności. Oto kompletny przykład dla aplikacji śledzącej dostawę jedzenia:
import ActivityKit
import Foundation
struct DeliveryActivityAttributes: ActivityAttributes {
// Dane niezmienne, ustawiane raz, przy starcie aktywności
let orderId: String
let restaurantName: String
let estimatedDeliveryTime: Date
// Dane mutowalne, aktualizowane podczas życia aktywności
public struct ContentState: Codable, Hashable {
var driverName: String
var driverProgress: Double // 0.0 ... 1.0
var currentStage: DeliveryStage
var minutesRemaining: Int
}
enum DeliveryStage: String, Codable, Hashable {
case preparing, onTheWay, arriving, delivered
}
}
Kluczowa uwaga: ContentState musi być w pełni Codable i Hashable, bo system serializuje go do APNs i deduplikuje aktualizacje. Trzymaj go małym (zalecam <4 KB), bo cała ramka push z payloadem nie może przekroczyć 4096 bajtów. Wszelkie obrazy podawaj jako nazwy z asset catalogu rozszerzenia. Nie próbuj przesyłać Data w state, ja kiedyś tak zrobiłem i payload odbił się od APNs z błędem PayloadTooLarge.
Uruchamianie, aktualizowanie i kończenie Live Activity
Aktywność uruchamia się z aplikacji głównej (lub przez Push-to-Start) wywołaniem Activity.request. W iOS 26 API jest w pełni async/await i wymaga obsługi błędów. Najczęstszym powodem niepowodzenia jest wyłączone uprawnienie w Ustawieniach albo limit ośmiu jednoczesnych aktywności tej samej aplikacji:
dismissalPolicy kontroluje, kiedy karta znika z ekranu blokady. .immediate ukrywa od razu, .default przez 4 godziny pozostawia ją w historii, a .after(Date) daje precyzyjną kontrolę. Dla scenariuszy „już dostarczone, ale niech zostanie 5 minut z potwierdzeniem" sam zwykle używam .after.
Projektowanie Dynamic Island: trzy stany prezentacji
Dynamic Island ma trzy oddzielne tryby renderowania, które musisz zaprojektować osobno. System wybiera, który wyświetlić, na podstawie kontekstu (czy ekran jest aktywny, czy są inne aktywności, czy użytkownik dotyka wyspy). Pominięcie któregokolwiek skończy się rozmytym, generycznym fallbackiem albo nawet odrzuceniem w App Store Review:
compactLeading / compactTrailing: dwa małe widoki po lewej i prawej stronie kamery, ~50 pt szerokości każdy. Idealne dla ikony stanu i wskaźnika postępu.
minimal: jeden mikro-widok pojawiający się, gdy w tle są dwie aktywności i twoja jest zminimalizowana. ~28 pt.
expanded: pełna karta po długim naciśnięciu wyspy lub gdy aktywność jest najnowsza. Tu mieścisz wszystkie szczegóły.
Oto pełna konfiguracja widoku dla naszej aktywności dostawy z użyciem Liquid Glass dostępnego w iOS 26:
Zwróć uwagę na keylineTint. To akcent koloru wokół wyspy, który Apple zaleca, by wizualnie wiązać aktywność z brandem aplikacji. Zbyt jaskrawe kolory są filtrowane przez system, więc trzymaj się palety semantycznej.
Push-to-Start i powiadomienia APNs
Push-to-Start, wprowadzony w iOS 17.2 i ulepszony w iOS 26, pozwala backendowi uruchomić Live Activity bez konieczności wcześniejszego otwarcia aplikacji. Klient rejestruje globalny token po stronie ActivityKit (taki, który nie wygasa przy zamknięciu aplikacji), a backend wysyła specjalne powiadomienie APNs z apns-push-type: liveactivity i apns-priority: 10. System tworzy aktywność i przekazuje początkowy ContentState.
Rejestracja tokenu globalnego po stronie klienta wygląda tak:
import ActivityKit
@MainActor
func registerForPushToStart() async {
for await data in Activity<DeliveryActivityAttributes>.pushToStartTokenUpdates {
let token = data.map { String(format: "%02x", $0) }.joined()
await BackendAPI.registerPushToStartToken(token)
}
}
Po stronie backendu payload APNs musi zawierać kompletny attributes i content-state. Oto przykład w formie JSON, którą wysyłamy na adres api.push.apple.com/3/device/<token>:
Do aktualizacji już istniejącej aktywności używasz tego samego endpointu, ale z "event": "update" i tokenem konkretnej aktywności (nie globalnym push-to-start). Zakończenie to "event": "end" z opcjonalnym polem dismissal-date. Pełną specyfikację podaje oficjalna dokumentacja ActivityKit, a do generowania tokenów JWT dla APNs używaj kluczy z włączoną opcją „APNs" w Apple Developer Portal. Token p8 ma 30-dniową ważność i można go cachować.
Interaktywne przyciski z App Intents
Od iOS 17 Live Activities mogą zawierać przyciski wykonujące akcje przez App Intents, bez otwierania aplikacji. W iOS 26 ten flow zyskał wsparcie dla zwracania nowego ActivityContent bezpośrednio z perform(), dzięki czemu nie trzeba osobno wywoływać activity.update. To upraszcza interakcje typu „kontynuuj timer", „pauza", „następna runda":
Kluczowe: LiveActivityIntent ma domyślnie openAppWhenRun = false, więc akcja wykonuje się w procesie Widget Extension. Oznacza to, że nie masz dostępu do pełnego stanu aplikacji ani do bazy SwiftData, jeśli nie jest w App Group. Jeśli potrzebujesz odczytać model domenowy, opisałem ten flow szerzej w artykule o App Intents w iOS 26. Kontekst Live Activity jest tam identyczny.
Limity, zużycie baterii i best practices
ActivityKit ma kilka twardych limitów, których łamanie kończy się cichym rzucaniem aktywności przez system. Trzymaj je w głowie od pierwszego dnia projektowania:
8 jednoczesnych aktywności per aplikacja. Dziewiąty Activity.request rzuca ActivityAuthorizationError.activityLimitExceeded.
4 KB payload APNs łącznie z aps, content-state i attributes. Większe są odrzucane przez APNs z błędem PayloadTooLarge.
12 godzin maksymalnego życia (iOS 26), po czym aktywność automatycznie wchodzi w stale-state i znika w ciągu 8 godzin lub przy dismissalPolicy.
~120 aktualizacji push dziennie z frequentUpdates, ~50 bez. System throttluje wizualnie, a nie odrzuca. Twoje update'y dochodzą, ale przestają się pojawiać na zablokowanym ekranie.
30 MB pamięci w procesie Widget Extension. Nie ładuj dużych obrazów, nie używaj Core ML.
Dla zużycia baterii dwie reguły mają największe znaczenie. Po pierwsze, ustaw realistyczny staleDate. Dzięki temu system wie, kiedy może uśpić twoją aktywność i nie próbuje renderować nieaktualnych danych. Po drugie, relevanceScore 100 zachowuj dla jednej, najbardziej aktualnej aktywności. Pozostałe degraduj do 50–80, bo to wpływa na to, którą wyspę użytkownik zobaczy najpierw. (W moim ostatnim projekcie zignorowanie tej reguły spowodowało, że aktywność dostawy „przegrywała" z aktywnością timera, co frustrowało użytkowników.)
Pod kątem designu trzymaj się wytycznych Apple HIG dla Live Activities. Typografia musi być czytelna z odległości metra, kontrast minimum WCAG AA (4.5:1), a animacje krótkie (poniżej 600 ms). W iOS 26 polecam też testowanie pod Liquid Glass, a szczegóły jak komponować widoki z systemowym tłem omówiłem w przewodniku po Liquid Glass w SwiftUI.
Jeśli budujesz UI prowadzące użytkownika do funkcji Live Activity (np. tutorial „włącz powiadomienia"), polecam zintegrować to z TipKit. Wzorce opisałem w przewodniku po TipKit w SwiftUI, który dobrze komponuje się z onboardingiem ActivityKit.
Często zadawane pytania
Czym różni się Live Activity od zwykłego widgetu?
Widget aktualizuje się przez timeline z ograniczeniem ~1 update'u na 15 minut, działa głównie na ekranie głównym i nie ma cyklu życia. Live Activity to aktywnie aktualizowana karta przez ActivityKit z dedykowanym miejscem na ekranie blokady i w Dynamic Island, gwarantowaną widocznością oraz jasnym cyklem start → update → end.
Jak długo Live Activity może być aktywna w iOS 26?
Maksymalnie 12 godzin w trybie aktywnym (z update'ami) plus do 8 godzin w stanie stale, zanim system ją zakończy. Możesz wcześniej zakończyć przez activity.end(_:dismissalPolicy:) z polityką .immediate, .default lub .after(Date).
Czy Live Activities działają na iPhone bez Dynamic Island?
Tak. Na iPhone 13 i starszych oraz wszystkich modelach SE aktywność pojawia się tylko na ekranie blokady i w widoku StandBy. Wystarczy zaprojektować widok lock-screen przez parametr for: w ActivityConfiguration. Xcode nie wymaga osobnego targetu.
Dlaczego moja Live Activity nie aktualizuje się po wysłaniu push?
Najczęstsze przyczyny: błędne attributes-type (musi być pełną nazwą struktury), payload powyżej 4096 bajtów, nieprawidłowy token (push-to-start vs activity update), wyczerpany dzienny limit aktualizacji lub apns-priority ustawiony na 5 zamiast 10. Sprawdź odpowiedź APNs, bo błędy są opisane w nagłówku apns-id oraz ciele JSON.
Czy mogę używać SwiftData lub Core Data w Live Activity?
Tylko jeśli kontener jest w App Group współdzielonym między aplikacją a Widget Extension. Pamiętaj o ograniczeniu pamięci do 30 MB. Odczytuj minimalne zapytania, nigdy nie ładuj pełnego modelu domenowego. Dla większości scenariuszy lepiej cache'ować snapshot w ContentState aktywności.
Praktyczny przewodnik po TipKit w SwiftUI na iOS 26. Konfiguracja TipsCenter, reguły eligibility, popoverTip vs TipView, grupy wskazówek i testowanie z gotowymi przykładami kodu.
Praktyczny przewodnik po frameworku App Intents w iOS 26. Pokazujemy, jak budować AppIntent, AppShortcut, AppEntity oraz nowość WWDC25 — Interactive Snippets, czyli interaktywne widoki SwiftUI w Siri i Shortcuts bez otwierania aplikacji.
Xcode 26.3 wprowadza kodowanie agentowe — AI, który nie tylko pisze kod, ale kompiluje, testuje i weryfikuje UI. Dowiedz się, jak skonfigurować Claude Agent, MCP i Skills, żeby pracować szybciej ze Swift i SwiftUI.