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 iOS 26: Kompletný Návod (2026)

Aktualizované: 17. augusta 2026

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:

BudgetView(entry: entry)
    .widgetURL(URL(string: "svetappy://budget/today"))

// V systemMedium/Large:
HStack {
    Link(destination: URL(string: "svetappy://budget/today")!) {
        Text("Dnes")
    }
    Link(destination: URL(string: "svetappy://budget/week")!) {
        Text("Tyzden")
    }
}

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:

  1. Voláte WidgetCenter.shared.reloadTimelines(ofKind:) po zmene dát? Bez toho systém čaká, kým vyprší posledný vstup.
  2. Používate správne kind? Reťazec sa musí zhodovať s tým, čo ste zadali v StaticConfiguration(kind:).
  3. Máte App Group správne nastavenú v oboch targetoch? Bez zdieľaného kontajnera widget číta prázdny UserDefaults.
  4. 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í.
  5. 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.

Editorial Team
O Autorovi Editorial Team

Our team of expert writers and editors.