WidgetKit v iOS 26: Kompletný sprievodca interaktívnymi widgetmi, Control Center a StandBy (2026)
Praktický sprievodca WidgetKit v iOS 26: TimelineProvider, interaktívne widgety cez App Intents, Control Center, StandBy, deep linking a diagnostika, prečo sa widget neaktualizuje. Vrátane Swift ukážok.
WidgetKit v iOS 26 je Apple framework pre tvorbu widgetov, ktoré bežia na Home Screen, Lock Screen, v StandBy režime, na macOS ploche, na Apple Watch a od iOS 18 aj v Control Center, s podporou interaktívnych tlačidiel a prepínačov cez App Intents bez potreby otvoriť aplikáciu. Tento sprievodca vás krok za krokom prevedie tvorbou moderného widgetu vrátane TimelineProvidera, ControlWidgetu, konfigurácie cez AppIntentConfiguration a diagnostikou najčastejšieho problému: prečo sa widget neaktualizuje.
WidgetKit v iOS 26 podporuje päť rodín widgetov (systemSmall/Medium/Large/ExtraLarge, accessory) plus ControlWidget pre Control Center a Lock Screen.
Interaktívne widgety fungujú cez Button a Toggle naviazané na AppIntent. Kód beží v procese widgetu, nie v aplikácii.
Systém nezaručuje presný čas obnovenia timeline. Apple oficiálne uvádza rozpočet ~40 až 70 obnovení denne pre bežný widget.
Modifikátor containerBackground(.fill.tertiary, for: .widget) je od iOS 17 povinný. Bez neho widget zbledne v StandBy a Lock Screen.
Control Center widgety (ControlWidget) sú samostatný typ, ktorý sa dá pripnúť aj na uzamknutú obrazovku a spúšťacie tlačidlo Action Button.
Deep linking rieši widgetURL(_:) pre celý widget a Link(destination:) pre časti systemMedium/Large.
Čo je WidgetKit a čo je nové v iOS 26
WidgetKit je framework predstavený na WWDC 2020, ktorý nahradil staré Today Extensions. V iOS 26 dosahuje zrelú podobu: každý widget je samostatný app extension, ktorý beží v izolovanom procese a komunikuje so systémom cez sériu timelines. Systém rozhoduje, kedy widget vykreslí, a vy iba dodávate stavy vopred spolu s dátumami, kedy sú platné.
Novinky v iOS 26 sú predovšetkým hlbšia integrácia s Liquid Glass dizajnom, širšia paleta veľkostí (nová systemExtraLarge na iPade), ControlWidget rozšírený o pripínanie na Lock Screen a Action Button, a spresnené správanie StandByMode vrátane nočného červeného tónu. Interaktívne widgety, ktoré dorazili v iOS 17, zostávajú kľúčovou zbraňou: používateľ prepne stav priamo v mriežke Home Screen, kód beží vo widget extension a UI sa obnoví do niekoľkých stoviek milisekúnd.
Aby ste získali z tohto textu maximum, mali by ste ovládať SwiftUI a základy App Intents. Ak nie, prečítajte si najprv náš sprievodca App Intents v iOS 26, kde detailne rozoberáme, ako intent registruje, prijíma parametre a integruje sa so Siri.
Anatómia Widget Extension a WidgetBundle
Widget nikdy nežije priamo v hlavnej aplikácii. Pridáte cez File → New → Target → Widget Extension nový cieľ, ktorý má vlastný Info.plist, vlastný bundle identifier a najmä vlastný proces. Xcode vygeneruje kostru so štruktúrou @main struct MyWidgetBundle: WidgetBundle, ktorá je vstupným bodom pre všetky widgety, ovládacie prvky a Live Activities balíčka.
Ak plánujete viacero widgetov alebo kombináciu Home Screen widgetu, Control Center widgetu a Live Activity, patria všetky do jedného WidgetBundle:
import WidgetKit
import SwiftUI
@main
struct DennyBudgetBundle: WidgetBundle {
var body: some Widget {
DennyBudgetWidget() // Home Screen + Lock Screen
DennyBudgetControl() // Control Center + Action Button
DennyBudgetLiveActivity() // ak pouzivate ActivityKit
}
}
Widget extension zdieľa kód s hlavnou aplikáciou cez App Group. V capabilities pridáte rovnaké ID (napr. group.sk.svetappy.dennybudget) do oboch targetov a potom cez UserDefaults(suiteName:) alebo FileManager.containerURL(forSecurityApplicationGroupIdentifier:) pristupujete k dátam odkiaľkoľvek. Bez App Group widget vidí len svoje vlastné piesočnisko a hlavnú aplikáciu nevie osloviť.
TimelineProvider a životný cyklus dát
Srdcom každého klasického widgetu je typ, ktorý implementuje TimelineProvider alebo modernejší AppIntentTimelineProvider. Provider má tri metódy: placeholder(in:) pre okamžitú kostru pri prvom pridaní widgetu, snapshot(for:in:) pre náhľad v galérii widgetov a timeline(for:in:), ktorá vráti pole TimelineEntry plus stratégiu, čo urobiť po vypršaní posledného vstupu.
struct BudgetEntry: TimelineEntry {
let date: Date
let spent: Decimal
let limit: Decimal
}
struct BudgetProvider: TimelineProvider {
func placeholder(in context: Context) -> BudgetEntry {
BudgetEntry(date: .now, spent: 0, limit: 50)
}
func getSnapshot(in context: Context,
completion: @escaping (BudgetEntry) -> Void) {
completion(BudgetEntry(date: .now, spent: 12.5, limit: 50))
}
func getTimeline(in context: Context,
completion: @escaping (Timeline<BudgetEntry>) -> Void) {
let store = BudgetStore.shared // cita z App Group
let now = Date.now
let entries = (0..<6).map { offset in
let date = now.addingTimeInterval(Double(offset) * 3600)
return BudgetEntry(date: date,
spent: store.spent(at: date),
limit: store.limit)
}
// po 6 hodinach pozadaj systemu o novy timeline
completion(Timeline(entries: entries, policy: .atEnd))
}
}
Politika obnovenia .atEnd hovorí systému, aby zavolal getTimeline znova hneď po vypršaní posledného vstupu. .after(date) definuje presný minimálny čas a .never hovorí, že o obnovenie požiada aplikácia sama cez WidgetCenter.shared.reloadTimelines(ofKind:). Presné časy nie sú nikdy garantované; systém rozhodne podľa energie, používania aplikácie a rozpočtu (viď sekciu nižšie).
Ako vytvoriť interaktívny widget v SwiftUI
Od iOS 17 môžete v tele widgetu použiť Button alebo Toggle a naviazať ich na AppIntent. Systém pri stlačení spustí intent priamo v procese widget extension, počká na jeho perform() a následne prekreslí UI, bez skoku do aplikácie.
import AppIntents
import WidgetKit
import SwiftUI
struct AddCoffeeIntent: AppIntent {
static var title: LocalizedStringResource = "Zapisat kavu"
static var description = IntentDescription("Prida jednu kavu do dennej statistiky.")
func perform() async throws -> some IntentResult {
try await BudgetStore.shared.log(item: .coffee, amount: 3.5)
// vyziada obnovenie vsetkych widgetov tohto druhu
WidgetCenter.shared.reloadTimelines(ofKind: "DennyBudgetWidget")
return .result()
}
}
struct BudgetView: View {
let entry: BudgetEntry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Dnes minute")
.font(.caption).foregroundStyle(.secondary)
Text(entry.spent, format: .currency(code: "EUR"))
.font(.title2.bold())
.contentTransition(.numericText())
Button(intent: AddCoffeeIntent()) {
Label("+ Kava", systemImage: "cup.and.saucer.fill")
}
.buttonStyle(.borderedProminent)
.tint(.brown)
}
.containerBackground(.fill.tertiary, for: .widget)
}
}
Podľa oficiálnej Apple dokumentácie WidgetKit intent musí byť „krátky a bez UI". Nikdy neotvárajte pickery, sheet-y ani nezobrazujte alerty priamo z perform(). Ak potrebujete používateľský vstup, cez openIntent otvorte aplikáciu.
Control Center widgety v iOS 26
Control Center prešiel v iOS 18 kompletnou reformou a v iOS 26 sa jeho ekosystém stabilizuje. Vaša aplikácia môže poskytnúť dva typy Control Center dlaždíc: ControlWidgetButton (jednorazová akcia, napr. „Otvor svetlo") a ControlWidgetToggle (dvojstavový prepínač). Používateľ si ich potom môže pripnúť aj na uzamknutú obrazovku alebo priradiť tlačidlu Action Button.
import WidgetKit
import SwiftUI
import AppIntents
struct DennyBudgetControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "sk.svetappy.dennybudget.control") {
ControlWidgetButton(action: AddCoffeeIntent()) {
Label("Kava +", systemImage: "cup.and.saucer.fill")
}
}
.displayName("Rychla kava")
.description("Prida kavu do dnesnej statistiky bez otvorenia aplikacie.")
}
}
Rozdiel oproti klasickému Home Screen widgetu je zásadný. Control Center dlaždice nemajú TimelineProvider, nemajú vlastné pozadie a Apple striktne diktuje ich vzhľad: dostanete iba Label, systém sa postará o zvyšok. To ich robí extrémne rýchlymi na naklikanie a Apple ich Human Interface Guidelines pre controls popisujú ako „jednoúčelové skratky".
Widgety pre Lock Screen, StandBy a watchOS
WidgetKit používa rovnaký kód pre všetky povrchy. Kľúčom je deklarovať supportedFamilies a v tele widgetu preskúmať widgetFamily environment premennú, aby ste vykreslili primerané rozloženie:
struct DennyBudgetWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "DennyBudgetWidget",
provider: BudgetProvider()) { entry in
BudgetSurface(entry: entry)
}
.configurationDisplayName("Denny budget")
.description("Zobrazi kolko ste dnes minuli.")
.supportedFamilies([
.systemSmall, .systemMedium, .systemLarge,
.accessoryCircular, .accessoryRectangular, .accessoryInline
])
}
}
struct BudgetSurface: View {
@Environment(\.widgetFamily) private var family
let entry: BudgetEntry
var body: some View {
switch family {
case .accessoryCircular:
Gauge(value: Double(truncating: entry.spent as NSNumber),
in: 0...Double(truncating: entry.limit as NSNumber)) {
Image(systemName: "eurosign")
}
.gaugeStyle(.accessoryCircular)
case .accessoryInline:
Text("Dnes: \(entry.spent, format: .currency(code: "EUR"))")
default:
BudgetView(entry: entry)
}
}
}
StandBy režim (nočný stojan iPhonu na MagSafe) používa rodiny systemSmall a systemMedium, ale po zotmení systém prekryje widget červeným filtrom. Ak spoliehate na farbu ako informačný kanál, radšej použite tvar alebo textovú indikáciu. Apple Watch využíva accessory rodiny; ich relevancia pre Smart Stack sa riadi cez relevance na TimelineEntry. Vyššie skóre znamená, že widget vyskočí navrch, keď je aktuálny.
Konfigurovateľné widgety s AppIntentConfiguration
Klasický StaticConfiguration vytvorí jeden widget bez parametrov. Ak chcete, aby si používateľ v dialógu Edit Widget vybral, ktorý účet alebo mesto sa má zobraziť, prepnite na AppIntentConfiguration s WidgetConfigurationIntent:
struct BudgetConfigIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Vyber ucet"
@Parameter(title: "Ucet") var account: AccountEntity
}
struct BudgetProviderConfigurable: AppIntentTimelineProvider {
func timeline(for configuration: BudgetConfigIntent,
in context: Context) async -> Timeline<BudgetEntry> {
let entries = await BudgetStore.shared.entries(for: configuration.account)
return Timeline(entries: entries, policy: .atEnd)
}
// placeholder a snapshot podobne
}
struct DennyBudgetWidget: Widget {
var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: "DennyBudgetWidget",
intent: BudgetConfigIntent.self,
provider: BudgetProviderConfigurable()
) { entry in BudgetSurface(entry: entry) }
}
}
Typ AccountEntity musí byť AppEntity s implementovaným AppEntityQuery, aby systém vedel zobraziť zoznam možností. Rovnaký princíp funguje aj pre Live Activities a Dynamic Island, ktoré tiež zdieľajú ekosystém App Intents.
Deep linking a widgetURL, ako otvoriť konkrétnu obrazovku
Klepnutie na widget štandardne otvorí aplikáciu. S modifikátorom widgetURL(_:) viete odovzdať konkrétny univerzálny link a v aplikácii ho spracujete cez onOpenURL alebo NavigationLink(value:). Pre systemMedium a systemLarge môžete v rôznych častiach widgetu použiť viacero Linkov, ktoré navigujú na rôzne obrazovky:
Na strane aplikácie budete typicky používať NavigationStack s navigationDestination(for:). Celý vzor podrobne rozpisujeme v článku o navigácii v SwiftUI a Router patterne.
Prečo sa môj widget neaktualizuje?
Úprimne, toto je najčastejšie hlásený problém a takmer nikdy nejde o chybu WidgetKit-u. Ja sám som pri poslednom projekte pol dňa hľadal, prečo sa widget po zápise do App Group neobnovuje, kým mi neucvaklo, že som zabudol zavolať reloadTimelines(ofKind:) z hlavnej aplikácie. Systém nezaručuje konkrétny čas obnovenia. Apple v Keeping a widget up to date dokumentácii uvádza, že bežný widget dostane približne 40 až 70 aktualizácií denne, s tým, že rozpočet sa zdieľa medzi všetky widgety balíčka a klesá po každom automatickom prekreslení.
Kontrolný zoznam pre diagnostiku:
Voláte WidgetCenter.shared.reloadTimelines(ofKind:) po zmene dát? Bez toho systém čaká, kým vyprší posledný vstup.
Používate správne kind? Reťazec sa musí zhodovať s tým, čo ste zadali v StaticConfiguration(kind:).
Máte App Group správne nastavenú v oboch targetoch? Bez zdieľaného kontajnera widget číta prázdny UserDefaults.
Nevraciate príliš krátku timeline? Ak generujete iba jednu položku každých pár sekúnd, systém vás rýchlo obmedzí.
Netestujete v Low Power Mode? V ňom systém drasticky obmedzí background aktivity vrátane obnovenia widgetov.
Pre profilovanie výkonu widgetu použite Instruments. Celý postup vrátane os_signpost intervalov je popísaný v našom článku o profilovaní Swift aplikácií s os_signpost.
Bežné chyby a osvedčené postupy
Tak, a teraz zoznam najbolestivejších pascí, na ktoré natrafíte pri produkčnom nasadení widgetov v iOS 26:
Zabudnutý containerBackground(...): bez neho widget na Lock Screen a StandBy vyzerá polopriehľadne a rozostrene, pretože systém nevie, kam natiahnuť pozadie.
Sťahovanie dát v getTimeline: táto metóda by mala vrátiť do dvoch sekúnd. Cachujte v aplikácii, widget iba číta z App Group.
Vytváranie Date() mimo timeline entries: každý entry musí mať dopredu určený dátum, inak ho systém považuje za neplatný.
Nezavolanie reloadTimelines po push notifikácii: background push, ktorý aktualizuje dáta, musí explicitne vyžiadať obnovenie widgetu.
Preťaženie ControlWidget logikou: Control Center dlaždice sú jednoúčelové. Ak potrebujete zložitejšie UI, patrí to na Home Screen widget alebo do Live Activity.
Použitie @State vo widget view: widget je bezstavový, každé prekreslenie začína odznova. Stav patrí do dát v App Group alebo do intent handlera.
Ak sa venujete širšiemu SwiftUI dizajnu vrátane efektov skla a materiálov, ktoré widgety v iOS 26 preberajú, vrelo odporúčame prečítať aj sprievodcu Liquid Glass v SwiftUI. Pomôže vám zosúladiť vzhľad widgetu s ostatnými plochami systému.
Často kladené otázky
Aký je rozdiel medzi widgetom a Live Activity?
Widget je statická (alebo interaktívna) dlaždica, ktorá zobrazuje periodicky obnovované informácie na Home Screen, Lock Screen alebo v StandBy. Live Activity je krátkodobá „relácia" naviazaná na prebiehajúcu udalosť (jazda taxíkom, doručenie jedla), ktorá žije v Dynamic Island a na Lock Screen a končí po jej dokončení. Widget beží dlhodobo, Live Activity nanajvýš 8 hodín.
Koľko widgetov môže mať jedna aplikácia?
Neexistuje pevný horný limit počtu Widget deklarácií v WidgetBundle, ale Apple odporúča držať sa piatich až siedmich, aby galéria widgetov ostala prehľadná. Používateľ si každý typ môže pridať v ľubovoľnom počte inštancií na ploche.
Môže widget spustiť kód na pozadí?
Nie priamo. Widget má krátky okamih na vykreslenie a interaktívne intenty spúšťa systém synchrónne pri klepnutí. Ak potrebujete plánovanú prácu, kombinujte widget s BGTaskScheduler alebo Push notifikáciami v hlavnej aplikácii, ktoré potom zavolajú WidgetCenter.shared.reloadTimelines(ofKind:).
Ako pridám widget do Control Center v iOS 26?
Implementujte ControlWidget v rovnakom Widget Extension targete ako Home Screen widget, pridajte ho do WidgetBundle, a používateľ si ho vyberie cez Nastavenia → Ovládacie centrum → Pridať ovládací prvok. Dlaždica sa dá tiež priradiť tlačidlu Action Button na iPhone 15 Pro a novších.
Prečo mi Xcode nezobrazí widget v Preview?
Widget Preview vyžaduje samostatný #Preview makro s parametrom as: .systemMedium (alebo inou rodinou) a musí vrátiť inštanciu vášho TimelineEntry. Ak stále nič nevidíte, skontrolujte, či je vybraná schéma widget targetu a či Preview beží na iOS 17+ simulátore.
Kompletný sprievodca Apple Foundation Models frameworkom v iOS 26. Naučte sa spúšťať on-device LLM v Swifte, používať @Generable makro a tool calling s reálnymi príkladmi.
Praktický sprievodca profilovaním Swift aplikácií pomocou os_signpost, OSSignposter a Instruments v Xcode 26. Interval eventy, Points of Interest, MetricKit a ako neurobiť z traceov nečitateľný chaos.
Ako pridať haptickú spätnú väzbu v SwiftUI cez SensoryFeedback a Core Haptics. Praktický sprievodca s hodnotami intenzity, AHAP súbormi a príkladmi z produkcie pre iOS 26.