Widget é snapshot + timeline, não um mini-app. Sem scroll arbitrário, sem rede a cada segundo. UI: swiftui-development. Dados: swiftdata / App Group. Linguagem: swift.
Live Activities RN: react-native-live-activities. Nativo ActivityKit é outro produto — não misture nesta skill salvo o repo já una os dois.
Stack e escopo
| Peça | Papel |
|---|---|
| WidgetKit | Extension target, TimelineProvider |
| SwiftUI | Views do widget (families) |
| App Intents | Botões / AppIntent no widget (iOS recente) |
| App Groups | Dados compartilhados com o app |
Organização
App/
...
MyWidget/ # extension
MyWidget.swift # WidgetBundle
Provider.swift
MyWidgetView.swift
- Extension magra: ler snapshot, montar view. Sem regra de negócio pesada.
- Deep link (
widgetURL/Link) para o app abrir o contexto certo.
Regras inegociáveis
- Families explícitas (
systemSmall,systemMedium,accessoryInline, …). Layout por family, não um único view esticado. - Timeline com
policyrealista (.after,.never). Budget de reload da Apple — nãoreloadAllem loop. - Placeholder /
redacted/privacySensitivepara dados pessoais na Lock Screen. - Dados: App Group UserDefaults/file ou snapshot escrito pelo app. Widget não faz login interativo.
- Main app chama
WidgetCenter.shared.reloadTimelines(ofKind:)depois de mutar dados. - Texto dinâmico:
Text+Datecom style (.relative,.timer) em vez de timer próprio.
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
let entry = Entry(date: .now, snapshot: store.read())
completion(Timeline(entries: [entry], policy: .after(.now.addingTimeInterval(15 * 60))))
}
UI
- Densidade alta, hierarquia imediata. Sem hamburger, sem navegação interna.
- Cores:
Colordo asset catalog / containerBackground. Dark/tinted:better-mobile-interface. - Imagens pequenas; sem vídeo.
- Ações:
Button(intent:)/ App Intent — trabalho curto; o pesado abre o app.
Dados e isolation
- Extension e app: mesmo App Group. Schema versionado do snapshot.
- SwiftData: store no group container se o deployment permitir; senão o app exporta um JSON/plist leve.
- Não bloqueie
getTimelinecom rede longa. Cache no app; widget lê disco.
Preview e QA
#PreviewcomWidgetPreviewContextem várias families.- Teste Lock Screen (redacted), StandBy, tinted (iOS 18+).
- Reloads: mutar no app e ver o widget atualizar.
Anti-padrões
- Mini SwiftUI app com NavigationStack no widget
- Polling de API a cada minuto
- Ignorar
context.isPreview/ placeholder - Uma view para small e extraLarge
- Esquecer reload após save no app
Integração com outras skills
- SwiftUI:
swiftui-development - Persistência:
swiftdata - Visual:
better-mobile-interface - Intents pesados podem abrir fluxos StoreKit no app (
storekit)
Critérios de conclusão
- Families com layout próprio
- Timeline policy consciente; reload no save do app
- App Group / snapshot estável
- Placeholder e privacy na lock
- Deep link para o contexto certo