Atualizado: 12 de setembro de 2026
Live Activities no iOS 26 são visualizações persistentes, atualizadas em tempo real, que aparecem na Tela de Bloqueio e na Dynamic Island para mostrar o estado atual de uma tarefa em andamento (corridas, pedidos, cronômetros ou partidas) sem exigir que o usuário abra o app. Elas são construídas com ActivityKit e SwiftUI, compartilhando o mesmo Widget Extension dos widgets tradicionais. Honestamente, desde os tempos em que a Apple ainda escrevia amostras em Objective-C, poucas APIs mudaram tanto a forma de pensar "presença" do app quanto essa. E no iOS 26 elas finalmente ganharam maturidade suficiente para produção.
O que são Live Activities no iOS 26?
Live Activities são uma extensão do WidgetKit especializada em conteúdo vivo. O sistema aloca um espaço fixo na Lock Screen e na Dynamic Island, e o seu app envia pequenas atualizações de estado que o SwiftUI renderiza. Diferente de um widget de timeline (que a Apple avalia e reagenda), uma Live Activity é event-driven: existe enquanto a tarefa existe, e desaparece quando ela termina.
No iOS 26 a API ganhou três melhorias importantes. Primeiro, o ActivityAttributes.ContentState agora exige que você forneça um staleDate em cada atualização. O sistema esmaece a UI quando esse instante passa, evitando que dados desatualizados fiquem exibidos indefinidamente. Segundo, o método Activity.request(_:content:pushType:) passou a aceitar o novo ActivityContent<State> em vez de um simples estado, permitindo empacotar prioridade e frescor. Terceiro, o suporte a frequent updates foi liberado sem entitlement especial em builds de desenvolvimento, o que acelera muito a iteração.
Casos de uso típicos incluem entregas de comida, corridas de aplicativo, embarque em voos, partidas esportivas ao vivo, timers de foco, sessões de treino e progresso de download. Se a informação muda ao longo de minutos e o usuário quer olhá-la de relance, é candidata a Live Activity. Se muda a cada segundo (áudio, música), o padrão certo continua sendo o Now Playing do MediaPlayer, e não uma Live Activity.
Como funciona a Dynamic Island no iPhone
A Dynamic Island é a área ao redor da câmera frontal em iPhones a partir do iPhone 14 Pro. Do ponto de vista da ActivityKit, ela é apenas um outro canvas de renderização da mesma Live Activity. O SwiftUI oferece quatro apresentações que você declara em um único bloco:
- Expanded: aparece quando o usuário toca e segura a ilha. Você tem quatro regiões (
leading,trailing,center,bottom). - Compact leading: canto esquerdo da ilha quando ela está no estado horizontal padrão.
- Compact trailing: canto direito, geralmente reservado para métrica principal (tempo restante, distância).
- Minimal: usado quando há mais de uma Live Activity simultânea e o sistema colapsa a sua em uma bolha ao lado da câmera.
Em iPhones sem Dynamic Island (modelos base pré-iPhone 15), as apresentações compactas e minimais são simplesmente ignoradas; a Live Activity continua aparecendo apenas na Lock Screen. Testar em ambos os simuladores é obrigatório porque muitos bugs de layout só se manifestam na ilha, com sua largura extremamente restrita (cerca de 44pt de altura). Não faz sentido colocar mais de duas informações lá. Escolha uma única métrica primária e um ícone, e pronto.
Configurando o Widget Extension e ActivityAttributes
Live Activities não são um novo target. Elas vivem dentro de um Widget Extension. Adicione um via File → New → Target → Widget Extension e marque "Include Live Activity". O Xcode 26 gera o Info.plist com NSSupportsLiveActivities = YES, tanto no app principal quanto na extensão. Sem isso, o sistema recusa silenciosamente qualquer Activity.request (eu perdi umas boas duas horas depurando isso no meu primeiro projeto de entrega).
Em seguida, defina um struct que descreva os dados. A separação entre atributos estáticos (definidos no início) e ContentState (mutável) é a pedra fundamental da API:
import ActivityKit
import SwiftUI
struct DeliveryAttributes: ActivityAttributes {
// Dados fixos: definidos ao iniciar a atividade
let orderId: String
let restaurantName: String
// Dados que mudam: cada update sobrescreve este struct
struct ContentState: Codable, Hashable {
var status: DeliveryStatus
var etaMinutes: Int
var courierName: String?
}
enum DeliveryStatus: String, Codable {
case preparing, onTheWay, arrived
}
}
O ContentState deve ser pequeno. O payload total é limitado a 4 KB depois de codificado, e cada byte extra viaja em todo push. Evite embutir imagens em base64: sirva URLs e deixe o SwiftUI carregá-las com AsyncImage, que agora suporta cache automático a partir do iOS 26.
Construindo a interface no SwiftUI
A interface é declarada em um Widget que retorna um ActivityConfiguration. Você fornece duas visualizações: uma para a Lock Screen e um DynamicIsland com as quatro apresentações. O truque é manter ambas visualmente coerentes usando os mesmos componentes internos.
struct DeliveryLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryAttributes.self) { context in
// Lock Screen / banner
LockScreenView(
restaurant: context.attributes.restaurantName,
state: context.state
)
.activityBackgroundTint(.black.opacity(0.85))
.activitySystemActionForegroundColor(.white)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Label(context.attributes.restaurantName, systemImage: "bag.fill")
}
DynamicIslandExpandedRegion(.trailing) {
Text("\(context.state.etaMinutes) min")
.font(.headline.monospacedDigit())
}
DynamicIslandExpandedRegion(.bottom) {
ProgressBar(status: context.state.status)
}
} compactLeading: {
Image(systemName: "bag.fill")
} compactTrailing: {
Text("\(context.state.etaMinutes)m")
.monospacedDigit()
} minimal: {
Image(systemName: "bag.fill")
}
.keylineTint(.orange)
}
}
}
Repare em .monospacedDigit(): sem isso, dígitos com larguras diferentes provocam um "salto" a cada segundo. É o tipo de detalhe que separa um app polido de um protótipo. Se você já leu meu tutorial sobre NavigationStack no SwiftUI para navegação programática, os mesmos princípios de layout composto se aplicam aqui. Divida em subviews pequenas para poder testá-las isoladamente em previews.
Iniciando, atualizando e finalizando uma Live Activity
Toda Live Activity tem três operações: request, update e end. O ciclo típico dentro do app principal fica assim:
import ActivityKit
@MainActor
enum DeliveryTracker {
static func start(orderId: String, restaurant: String) throws -> Activity<DeliveryAttributes> {
let attributes = DeliveryAttributes(orderId: orderId, restaurantName: restaurant)
let initialState = DeliveryAttributes.ContentState(
status: .preparing,
etaMinutes: 35,
courierName: nil
)
return try Activity.request(
attributes: attributes,
content: ActivityContent(
state: initialState,
staleDate: .now.addingTimeInterval(60 * 5)
),
pushType: .token
)
}
static func update(_ activity: Activity<DeliveryAttributes>,
state: DeliveryAttributes.ContentState) async {
await activity.update(
ActivityContent(state: state, staleDate: .now.addingTimeInterval(60 * 5))
)
}
static func finish(_ activity: Activity<DeliveryAttributes>) async {
await activity.end(
ActivityContent(state: activity.content.state, staleDate: .now),
dismissalPolicy: .after(.now + 30)
)
}
}
A dismissalPolicy merece atenção: .immediate some assim que a atividade termina; .default deixa o sistema decidir (até 4 horas); .after(Date) força uma janela específica. Para entregas, a janela de 30 segundos depois de "entregue" costuma ser o balanço ideal entre confirmação visual e não ficar poluindo a Lock Screen.
Como atualizar Live Activities via push notifications
Atualizar a atividade a partir do próprio app funciona enquanto o app está em execução. Mas para o caso realista (o usuário fechou o app e a corrida continua acontecendo), você precisa de pushes remotos. O fluxo tem três etapas: obter o push token, enviar para o backend, e disparar um push com o payload content-state.
Task {
for await pushToken in activity.pushTokenUpdates {
let hex = pushToken.map { String(format: "%02x", $0) }.joined()
try await api.registerLiveActivityToken(hex, activityId: activity.id)
}
}
No servidor, o payload APNs precisa do header apns-push-type: liveactivity, apns-topic: com.seuapp.bundle.push-type.liveactivity, e um body com event (update ou end), content-state, stale-date e timestamp. A documentação oficial da Apple sobre push updates para Live Activities mostra o JSON exato. Vale mantê-la aberta enquanto você implementa o serviço.
No iOS 26 a Apple aumentou a cota de frequent updates para até 240 pushes/hora quando você declara NSSupportsLiveActivitiesFrequentUpdates. Fora dessa cota, o sistema começa a descartar pushes silenciosamente. Monitore isso no Console via subsystem com.apple.ActivityKit. Se sua UI depende de haptics junto com a atualização, o padrão que mostrei em sensory feedback no SwiftUI com haptics continua sendo aplicável, mas só dispara quando o app está em foreground. Live Activities em background não vibram.
Deep linking da Live Activity para o app
O usuário quase sempre quer tocar na Live Activity para abrir o app na tela relevante. Existem dois mecanismos: widgetURL(_:), que trata a área inteira como um link, e Link, para regiões específicas na apresentação expanded.
LockScreenView(...)
.widgetURL(URL(string: "meuapp://order/\(context.attributes.orderId)"))
// Dentro da região expanded, cada Link pode apontar para lugares diferentes:
DynamicIslandExpandedRegion(.bottom) {
HStack {
Link(destination: URL(string: "meuapp://order/\(id)")!) {
Label("Ver pedido", systemImage: "bag")
}
Link(destination: URL(string: "meuapp://call/\(id)")!) {
Label("Ligar", systemImage: "phone.fill")
}
}
}
No app, esses URLs chegam via .onOpenURL na cena principal, e daí o roteamento se encaixa em qualquer arquitetura de navegação baseada em NavigationStack com path observável. A ideia é converter o URL em um valor tipado que representa a rota, adicionar ao NavigationPath, e deixar o navigationDestination resolver.
Se você quer ações interativas (botões que executam código sem abrir o app), a resposta no iOS 26 é AppIntent. Um botão dentro da Live Activity pode disparar um intent que marca a corrida como concluída, por exemplo. Cobri esse padrão em detalhe em App Intents 2.0 no iOS 26 com Interactive Snippets. A mesma API funciona aqui, com a diferença de que o intent precisa ser marcado como @Parameter-livre para ser embutível em widgets.
Testando Live Activities no simulador do Xcode 26
O simulador do Xcode 26 finalmente suporta o ciclo completo, inclusive Dynamic Island. Escolha um iPhone 16 Pro ou superior no seletor de simulador; nos modelos base a ilha não é renderizada. Para simular pushes remotos sem passar pelo APNs, o Xcode ganhou um mecanismo elegante: crie um arquivo .apns no projeto com o payload e arraste-o para o simulador rodando.
{
"aps": {
"timestamp": 1789520000,
"event": "update",
"content-state": {
"status": "onTheWay",
"etaMinutes": 12,
"courierName": "Ana"
},
"stale-date": 1789520300
}
}
Renomeie o arquivo para update.apns, arraste-o para o simulador, e o sistema aplica o payload como se viesse de um push real. Isso encurta drasticamente o loop de desenvolvimento. Antes do iOS 26 era comum precisar de um servidor local só para exercitar transições de estado. Combine com o novo botão "Simulate Activity Update" na barra de debug do Xcode para variar rapidamente ETAs e status.
Vale também escrever snapshot tests do widget usando PreviewProvider com #Preview("Dynamic Island Compact", as: .dynamicIsland(.compact), using: DeliveryAttributes.preview). Isso captura regressões visuais sem precisar rodar o app.
Boas práticas e limites do ActivityKit
Depois de várias entregas em produção com essa API, algumas regras se firmaram na minha rotina:
- Sempre defina
staleDate. No iOS 26 é obrigatório, mas mesmo se não fosse, um estado que nunca fica "velho" é mentira sobre o sistema real. - Nunca coloque texto secreto na Lock Screen (nome de destinatário, endereço completo, valor). Live Activities aparecem sem desbloqueio; trate como uma superfície pública.
- Reduza o payload agressivamente. Se o campo é derivável (barra de progresso a partir do ETA), calcule no widget; não envie no push.
- Trate a atividade como fonte-derivada, não fonte-de-verdade. Se o app cair e reiniciar, você recupera atividades ativas via
Activity.activitiese re-sincroniza a partir do backend. - Use uma única cor de "keyline" alinhada com a marca. A Dynamic Island expandida usa esse tint como acento.
- Encerre com
.after(Date)e não.default, para você controlar quando a atividade some da UI do usuário.
O limite duro que sempre vale lembrar: uma Live Activity pode ficar até 12 horas ativa recebendo updates e até mais 4 horas em estado "ended" antes de ser removida definitivamente. Passou disso, o sistema encerra e você precisa iniciar uma nova. Para o detalhamento oficial das diretrizes de interface humana da Apple para Live Activities, sempre revalide contra a versão publicada. O documento muda a cada release principal do iOS.
Perguntas frequentes
Qual a diferença entre Live Activities e widgets tradicionais?
Widgets são baseados em timelines pré-agendadas pelo app e mostram informação atemporal (clima, calendário). Live Activities existem apenas enquanto uma tarefa está acontecendo, são atualizadas por eventos (ou pushes), e podem aparecer na Dynamic Island, o que widgets normais não podem.
Live Activities funcionam em quais dispositivos?
Todos os iPhones com iOS 16.1 ou superior suportam Live Activities na Lock Screen. A Dynamic Island exige iPhone 14 Pro ou superior. Não há suporte oficial em iPads no iOS 26; use widgets convencionais para essa plataforma.
Como saber se uma Live Activity já está ativa antes de iniciar outra?
Consulte Activity<SeuAttributes>.activities: é uma coleção sincronizada com o sistema. Filtre por attributes.orderId (ou identificador equivalente) para evitar duplicatas. É comum manter esse cache dentro de um @MainActor service.
Preciso de um servidor para usar Live Activities?
Não para começar. activity.update(_:) chamado a partir do próprio app já basta enquanto o app está em execução. Um backend só é necessário quando você quer manter a Live Activity atualizada com o app fechado, e nesse caso o servidor envia pushes via APNs com apns-push-type: liveactivity.
Por que minha Live Activity não aparece na Dynamic Island?
As causas mais comuns são: rodar em um simulador de iPhone sem ilha (use um iPhone 16 Pro), esquecer de declarar as apresentações compactas no ActivityConfiguration, ou o usuário ter mais de duas Live Activities simultâneas. Nesse último caso o sistema colapsa algumas no modo minimal. Verifique também se NSSupportsLiveActivities está no Info.plist da extensão e do app principal.




