WidgetKit i iOS 26: Komplett Guide till Interaktiva Widgets, Control Center och Cross-Platform-utveckling

Praktisk guide till WidgetKit i iOS 26: bygg din första widget, gör den interaktiv med App Intents, publicera i Control Center och hantera skillnader mellan iPhone, iPad, Mac, Watch och Vision Pro.

WidgetKit iOS 26: Interaktiva Widgets Guide

Uppdaterad: 8 augusti 2026

WidgetKit är Apples ramverk för att bygga widgets som visas på hemskärmen, låsskärmen, i StandBy-läget, i Notification Center på macOS, på Apple Watch-urtavlan och (sedan iOS 18) i Control Center. I iOS 26 har WidgetKit fått bättre stöd för interaktivitet via App Intents, förfinade Control Center-kontroller, uppdaterade widgetstorlekar för visionOS 26 och stramare minnesregler. Den här guiden går igenom hela stacken: hur du bygger en widget, gör den interaktiv, publicerar den i Control Center och hanterar plattformsskillnader mellan iPhone, iPad, Mac, Watch och Vision Pro.

  • WidgetKit i iOS 26 kräver iOS 14 eller senare för grundläggande widgets, iOS 17 för interaktivitet och iOS 18 för Control Center-kontroller.
  • En widget består av tre delar: en TimelineProvider som levererar entries, en SwiftUI-vy som renderar dem och en Widget-konfiguration som binder ihop allt.
  • Interaktiva widgets bygger på App Intents. Bara Button och Toggle med ett intent:-argument fungerar; godtyckliga gester stöds inte.
  • Control Center-kontroller använder ControlWidget och delar samma App Intent-modell som Home Screen-widgets, vilket ger enorm kodåteranvändning.
  • Minnesbudgeten för widget-extensions är cirka 30 MB. Överskrids den kill:as processen utan varning, oavsett plattform.
  • Samma widget kan kompileras för iPhone, iPad, Mac (via Mac Catalyst eller native), watchOS och visionOS 26 med enbart plattformsspecifika supportedFamilies-anrop.

Hur fungerar WidgetKit-arkitekturen?

WidgetKit är ett deklarativt ramverk där du inte äger renderingsloopen. Systemet bestämmer när din widget ska visas och när dess vy ska ritas om. Din uppgift är att leverera en timeline, en ordnad lista av entries där varje entry innehåller ett datum och den data som ska visas då. Systemet väljer sedan rätt entry baserat på aktuell tid och renderar den associerade SwiftUI-vyn.

Den här designen är avsiktlig. Widgets körs inte kontinuerligt; de är statiska ögonblicksbilder som cachas av systemet och plockas fram vid behov. Det gör dem extremt strömsnåla, men innebär också att du inte kan köra godtycklig kod när användaren tittar på dem. All datahämtning måste ske i förväg när du bygger timelinen.

Tre huvudkomponenter samverkar:

  • TimelineProvider: ansvarar för att skapa placeholder-, snapshot- och timeline-entries. Det är här din datahämtning sker.
  • TimelineEntry: en enkel struct med ett date: Date och alla data widget-vyn behöver för att rendera sig själv.
  • Widget: deklarationen som binder ihop kind, konfiguration, provider och vy. Den registreras i en WidgetBundle.

På iOS 17 och senare rekommenderar Apple AppIntentTimelineProvider istället för klassiska IntentTimelineProvider. Den ersätter SiriKit intents med moderna App Intents och integrerar direkt med Apple Intelligence i iOS 26. Jag använder den konsekvent nedan, eftersom migreringen från gamla intents i ett tidigare projekt bara tog någon eftermiddag och sparade oss mycket felmeddelanden.

Vilka widgetfamiljer finns i iOS 26?

Widgetfamiljer bestämmer var din widget kan visas och i vilken storlek. iOS 26 stödjer följande värden av WidgetFamily:

FamiljPlattformarPlaceringUngefärlig storlek (iPhone 17 Pro)
systemSmalliOS, iPadOS, macOS, visionOSHemskärm / skrivbord170×170 pt
systemMediumiOS, iPadOS, macOS, visionOSHemskärm / skrivbord364×170 pt
systemLargeiOS, iPadOS, macOS, visionOSHemskärm / skrivbord364×382 pt
systemExtraLargeiPadOS, macOS, visionOSHemskärm / skrivbord780×382 pt (iPad)
accessoryCirculariOS (låsskärm/StandBy), watchOSLåsskärm, urtavla76×76 pt
accessoryRectangulariOS (låsskärm/StandBy), watchOSLåsskärm, urtavla172×76 pt
accessoryInlineiOS (låsskärm), watchOSOvanför klockanTextrad
accessoryCornerwatchOSCorner ComplicationsRundad hörnkomplikation

Notera att systemExtraLarge inte finns på iPhone. Försöker du deklarera den där händer helt enkelt ingenting (Xcode varnar inte heller). På visionOS 26 renderas alla system-familjer som svävande fönster i användarens miljö, med subtila glaseffekter som passar Liquid Glass. Om du vill fördjupa dig i det designspråket har jag skrivit en separat guide om Liquid Glass i SwiftUI.

Bygga din första widget steg för steg

Så, låt oss bygga något konkret. Vi gör en väderwidget som visar aktuell temperatur för användarens plats. Skapa först ett nytt Widget Extension-mål i Xcode 26 (File → New → Target → Widget Extension) och avmarkera "Include Configuration App Intent" om du vill börja enkelt. Vi lägger till konfigurationen längre ner.

import WidgetKit
import SwiftUI

struct WeatherEntry: TimelineEntry {
    let date: Date
    let temperature: Measurement<UnitTemperature>
    let condition: String
    let city: String
}

struct WeatherProvider: TimelineProvider {
    func placeholder(in context: Context) -> WeatherEntry {
        WeatherEntry(
            date: .now,
            temperature: .init(value: 18, unit: .celsius),
            condition: "Klart",
            city: "Stockholm"
        )
    }

    func getSnapshot(in context: Context, completion: @escaping (WeatherEntry) -> Void) {
        completion(placeholder(in: context))
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> Void) {
        Task {
            let current = try await WeatherService.shared.fetchCurrent()
            let entry = WeatherEntry(
                date: .now,
                temperature: current.temperature,
                condition: current.condition,
                city: current.city
            )
            // Ladda om timelinen om 30 minuter
            let refresh = Date.now.addingTimeInterval(30 * 60)
            completion(Timeline(entries: [entry], policy: .after(refresh)))
        }
    }
}

Providern har tre metoder som systemet anropar vid olika tidpunkter. placeholder används medan datan laddas och i widgetgalleriet vid preview. getSnapshot anropas när Siri, Spotlight eller Smart Stack behöver en snabb ögonblicksbild. getTimeline anropas när systemet vill ha en ny lista med entries att visa.

Vyn är helt vanlig SwiftUI:

struct WeatherWidgetView: View {
    let entry: WeatherEntry
    @Environment(\.widgetFamily) private var family

    var body: some View {
        switch family {
        case .systemSmall:
            VStack(alignment: .leading) {
                Text(entry.city).font(.caption).foregroundStyle(.secondary)
                Spacer()
                Text(entry.temperature.formatted(.measurement(width: .abbreviated)))
                    .font(.system(size: 32, weight: .bold, design: .rounded))
                Text(entry.condition).font(.caption)
            }
            .padding()
        case .accessoryCircular:
            Text(entry.temperature.formatted(.measurement(width: .narrow)))
                .font(.system(size: 14, weight: .bold))
        default:
            HStack {
                Text(entry.city)
                Spacer()
                Text(entry.temperature.formatted())
            }
            .padding()
        }
    }
}

Slutligen bindningen:

struct WeatherWidget: Widget {
    let kind: String = "WeatherWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: WeatherProvider()) { entry in
            WeatherWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Väder")
        .description("Aktuell temperatur för din plats.")
        .supportedFamilies([.systemSmall, .systemMedium, .accessoryCircular, .accessoryRectangular])
    }
}

Modifieraren containerBackground(_:for:) är obligatorisk från iOS 17. Utan den får du en tom bakgrund i StandBy och på låsskärmen. Använd .fill.tertiary som en säker default, eller egen färg för branding.

Hur gör man en widget interaktiv i iOS 26?

Interaktiva widgets introducerades i iOS 17 och bygger på App Intents. Regeln är strikt: enbart Button och Toggle med ett intent:-argument fungerar. Godtyckliga onTapGesture-modifierare eller NavigationLink ignoreras, eftersom widgets renderas out-of-process och systemet inte vet hur det ska serialisera generella closures.

Jag brände en halv arbetsdag på precis det här problemet när jag först provade. Så här ser rätt uppsättning ut.

Steg ett: definiera ett App Intent som utför handlingen. Om du inte har jobbat med App Intents tidigare rekommenderar jag min guide till App Intents i SwiftUI, som täcker grunderna.

import AppIntents
import WidgetKit

struct ToggleFavoriteIntent: AppIntent {
    static var title: LocalizedStringResource = "Växla favorit"

    @Parameter(title: "Artikel-ID")
    var articleID: String

    init() {}
    init(articleID: String) { self.articleID = articleID }

    func perform() async throws -> some IntentResult {
        let store = FavoriteStore.shared
        await store.toggle(articleID: articleID)
        WidgetCenter.shared.reloadTimelines(ofKind: "FavoritesWidget")
        return .result()
    }
}

Steg två: använd intenten i en Button inuti widget-vyn.

struct FavoritesWidgetView: View {
    let entry: FavoritesEntry

    var body: some View {
        VStack {
            ForEach(entry.articles) { article in
                HStack {
                    Text(article.title).lineLimit(1)
                    Spacer()
                    Button(intent: ToggleFavoriteIntent(articleID: article.id)) {
                        Image(systemName: article.isFavorite ? "star.fill" : "star")
                    }
                    .buttonStyle(.plain)
                }
            }
        }
        .padding()
    }
}

När användaren trycker på stjärnan körs intenten i appens huvudprocess (eller i en dedikerad "background app launch"-kontext på macOS). Efter perform() returnerar anropar vi WidgetCenter.shared.reloadTimelines(ofKind:) för att tvinga en omladdning, så användaren ser resultatet inom några hundra millisekunder.

Hur lägger man till en widget i Control Center?

Från iOS 18 kan tredjepartsappar leverera egna Control Center-kontroller via typen ControlWidget. På iOS 26 har API:et stabiliserats och delar nu samma App Intent-modell som Home Screen-widgets, vilket gör kodåteranvändningen nästan total.

Det finns två sorters kontroller:

  • ControlWidgetButton: engångshandling (ex. "Starta timer").
  • ControlWidgetToggle: on/off-tillstånd (ex. "Focus Mode").
import AppIntents
import WidgetKit
import SwiftUI

struct QuickTimerControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "com.example.QuickTimer") {
            ControlWidgetButton(action: StartTimerIntent(duration: 300)) {
                Label("5 min timer", systemImage: "timer")
            }
        }
        .displayName("Snabbtimer")
        .description("Startar en 5-minuters timer med ett tryck.")
    }
}

struct StartTimerIntent: AppIntent {
    static var title: LocalizedStringResource = "Starta timer"
    static var openAppWhenRun: Bool = false

    @Parameter(title: "Sekunder")
    var duration: Int

    init() {}
    init(duration: Int) { self.duration = duration }

    func perform() async throws -> some IntentResult {
        await TimerService.shared.start(duration: TimeInterval(duration))
        return .result()
    }
}

Registrera din kontroll i widget-bundlen precis som en vanlig widget:

@main
struct MyWidgetBundle: WidgetBundle {
    var body: some Widget {
        WeatherWidget()
        QuickTimerControl()
    }
}

Efter installation dyker kontrollen upp bland användarens tillgängliga kontroller i Control Center och kan även bindas till Action Button på iPhone 15 Pro och senare, eller till låsskärmens kontrollknappar från iOS 18 och framåt. Enligt Apples Human Interface Guidelines för controls bör kontrollen utföra en väl definierad handling. Motstå frestelsen att stoppa in flera relaterade funktioner i samma yta.

Plattformsskillnader: iPhone, iPad, Mac, Watch och visionOS

WidgetKit är kanske det mest genuint plattformsöverskridande ramverket Apple erbjuder. Samma kod kompilerar till fem plattformar, men det finns viktiga skillnader du måste hantera i praktiken.

iPhone och iPadOS

iPad stödjer systemExtraLarge (extra bred widget), vilket iPhone inte gör. iPad har också en dedikerad låsskärm sedan iPadOS 17 där accessory*-familjerna fungerar. Använd @Environment(\.widgetFamily) för att anpassa layouten och deklarera supportedFamilies plattformsspecifikt om det behövs.

macOS

På macOS bor widgets på skrivbordet (från macOS 14) och i Notification Center. Widgets kompilerade för iOS körs automatiskt på Apple Silicon-Macar via en Mac Catalyst-liknande brygga; en native macOS-version kan använda mus-hover-effekter via hoverEffect(_:). Watch-familjerna är inte tillgängliga.

watchOS

På watchOS ersätter widgets de klassiska Complications sedan watchOS 9. Använd accessoryCircular, accessoryRectangular, accessoryInline och accessoryCorner. En Smart Stack-widget kan aktivt föreslås av systemet baserat på tid och plats via RelevantContext.

visionOS 26

visionOS 26 renderar systemfamiljer som svävande 3D-fönster med subtil parallax. Använd ornament-liknande layouter försiktigt; texten måste vara läsbar på en meters avstånd. Från visionOS 26 stöds även widgetLevelOfDetail som anpassar innehåll beroende på hur nära användaren är fönstret. Det mönstret finns inte på någon annan plattform.

Timeline-strategier och batteriekonomi

WidgetKit stödjer tre TimelineReloadPolicy-värden:

  • .atEnd: ladda om när sista entryn i timelinen har passerat.
  • .after(Date): ladda om vid en specifik tidpunkt.
  • .never: ladda aldrig om automatiskt; be systemet göra det via WidgetCenter.shared.reloadTimelines(ofKind:).

Systemet garanterar inte att din widget uppdateras exakt när du bad om det. Apples dokumentation nämner en ungefärlig budget om 40–70 timeline-refreshes per widget per dag. Om användaren aldrig tittar på widgeten kan systemet välja att inte alls refresha den. Bygg därför tåligt: anta att din widget kan visa 2 timmar gamla data och rita en tydlig tidsstämpel om det är viktigt.

För push-drivna uppdateringar från servern (t.ex. sportresultat, aktiekurser) använd Apple Push Notification service med content-available: 1 och anropa reloadTimelines i appens push-handler. På iOS 26 finns även möjligheten att skicka background push direkt till widget-extensionen via reloadRelevances, vilket är betydligt strömsnålare.

Varför uppdateras inte min widget? Minne och prestanda

Den vanligaste orsaken till att en widget "slutar fungera" är att den överskred systemets minnesbudget och kill:ades utan varning. Widget-extensions har en hård gräns på cirka 30 MB residentminne, oavsett om enheten har 6 GB eller 16 GB RAM. Kryssar du över den gränsen dödas processen, och widgeten visar antingen sin sista cachade rendering eller en tom platshållare tills nästa refresh.

Praktiska konsekvenser:

  • Ladda aldrig fullstora bilder. Använd UIImage(named:) med förskalade assets, eller nedsampla med ImageIO.
  • Undvik tunga Core Data-graph-hämtningar; hämta bara det som behövs för entryn.
  • Bygg inte SwiftData-modeller med djupa relationer i widget-processen. Exportera platt DTO från huvudappen via App Groups istället. Se min SwiftData-guide för mönster som fungerar bra över processgränser.
  • Kör Instruments > Allocations mot Widget Extension-schemat innan release. Om peak-minnet överstiger 25 MB har du bara 5 MB marginal och kraschar då och då i produktion.

Andra vanliga orsaker till att widgets inte uppdateras: du glömde WidgetCenter.shared.reloadTimelines(ofKind:) efter en datamutation i appen, din TimelineReloadPolicy är .never, eller systemet har medvetet backat av på refresh-frekvensen på grund av låg batterinivå eller Low Power Mode.

Konfigurerbara widgets med AppIntentConfiguration

Om du vill låta användaren välja innehåll (t.ex. vilken stad väderwidgeten visar), använd AppIntentConfiguration istället för StaticConfiguration.

struct SelectCityIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Välj stad"

    @Parameter(title: "Stad", default: "Stockholm")
    var city: String
}

struct WeatherWidget: Widget {
    let kind: String = "WeatherWidget"

    var body: some WidgetConfiguration {
        AppIntentConfiguration(
            kind: kind,
            intent: SelectCityIntent.self,
            provider: WeatherProvider()
        ) { entry in
            WeatherWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

Din TimelineProvider måste nu ändras till AppIntentTimelineProvider, där varje metod tar en instans av intenten som andra argument. Där ligger användarens val.

struct WeatherProvider: AppIntentTimelineProvider {
    typealias Entry = WeatherEntry
    typealias Intent = SelectCityIntent

    func placeholder(in context: Context) -> WeatherEntry { ... }

    func snapshot(for configuration: SelectCityIntent, in context: Context) async -> WeatherEntry {
        await fetch(for: configuration.city)
    }

    func timeline(for configuration: SelectCityIntent, in context: Context) async -> Timeline<WeatherEntry> {
        let entry = await fetch(for: configuration.city)
        return Timeline(entries: [entry], policy: .after(.now.addingTimeInterval(1800)))
    }
}

App Intents integreras dessutom med Apple Intelligence i iOS 26. Konfigurerade widgets kan visas som förslag i Siri och Spotlight utifrån användarens rutiner. Du får det gratis så länge du deklarerar meningsfulla title- och description-strängar.

För detaljerade API-referenser, se Apples officiella WidgetKit-dokumentation, som uppdaterades i samband med Xcode 26.

Vanliga frågor

Vad är skillnaden mellan en widget och en Live Activity?

En widget visar statisk data som uppdateras via en förbestämd timeline; en Live Activity uppdateras i realtid via ActivityKit och lever bara medan en händelse pågår (t.ex. en pågående match eller matleverans). Widgets är alltid tillgängliga; Live Activities visas bara aktivt. Läs min Live Activities-guide för fördjupning i den senare.

Kan widgets köra godtycklig kod när användaren trycker på dem?

Nej. Från iOS 17 fungerar bara Button och Toggle med App Intents som interaktivitet. Andra gester och navigationsmönster stöds inte eftersom widgeten renderas out-of-process och systemet inte kan serialisera generella closures.

Fungerar samma widget-kod på iPhone, Apple Watch och Vision Pro?

Ja, i grunden. Timeline-logiken kan delas via ett Swift-paket. Skillnaderna ligger i vilka WidgetFamily-värden respektive plattform stödjer och vilka UI-metaforer som passar (kanttexter på watchOS, svävande fönster på visionOS, hover-effekter på macOS).

Hur mycket minne får en widget använda?

Cirka 30 MB residentminne. Överskrids gränsen dödas widget-processen utan felmeddelande. Nedsampla bilder, undvik djupa objektgrafer och profilera i Instruments innan release.

Hur ofta uppdaterar iOS min widget?

Apple garanterar inget exakt tal, men riktvärdet är 40–70 timeline-refreshes per widget per dag. Systemet minskar frekvensen om användaren sällan tittar på widgeten, om batterinivån är låg eller Low Power Mode är aktivt. Använd push för händelsedrivna uppdateringar snarare än korta polling-intervall.

Hiroshi Sato
Om Författaren Hiroshi Sato

Apple Platforms specialist building for iOS, macOS, visionOS, and the occasional watchOS app nobody asked for.