WidgetKit iOS 17+: Interactieve Widgets met App Intents

Volledige gids voor het maken van interactieve iOS-widgets met WidgetKit en App Intents. Knoppen, toggles, animaties en best practices voor iOS 17+ in 2026.

WidgetKit iOS 17+ met interactieve widgets en App Intents voor moderne iOS-applicaties

iOS 17 heeft WidgetKit gerevolutioneerd door native interactiviteit te introduceren. Widgets zijn niet langer statische weergaven: ze kunnen nu rechtstreeks vanaf het beginscherm reageren op gebruikersacties zonder de app te openen. Deze belangrijke evolutie steunt op het App Intents framework en biedt een vloeiende, moderne gebruikerservaring.

Wat dit artikel behandelt

Dit artikel behandelt het volledige proces van het maken van interactieve iOS 17+ widgets, van projectconfiguratie tot geavanceerde patronen met animaties en statusbeheer.

Architectuur van Interactieve Widgets

De interactiviteit van iOS 17+ widgets werkt via het App Intents framework. In tegenstelling tot traditionele deeplinks die de app zouden openen, maken App Intents het mogelijk om code rechtstreeks vanuit de widget uit te voeren en daarna automatisch de weergave te vernieuwen met de nieuwe gegevens.

InteractiveWidgetArchitecture.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// De architectuur is gebaseerd op drie hoofdcomponenten:
// 1. Widget Timeline Provider - levert de gegevens
// 2. Widget View - toont de interface met Button/Toggle
// 3. App Intent - voert de actie uit bij aanraking

struct TaskWidget: Widget {
    // Unieke widget-identificatie
    let kind: String = "TaskWidget"

    var body: some WidgetConfiguration {
        // StaticConfiguration voor widgets zonder parameters
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                // Verplicht voor App Intents
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Taken")
        .description("Beheer uw taken vanaf het beginscherm.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

De widget declareert zijn configuratie en specificeert de provider die de gegevens zal leveren. Het .containerBackground attribuut is verplicht sinds iOS 17 voor interactieve widgets.

Aanmaken van de Timeline Provider

De Timeline Provider bepaalt wanneer en hoe de widget wordt vernieuwd. Voor interactieve widgets moet hij ook reageren op wijzigingen veroorzaakt door App Intents.

TaskTimelineProvider.swiftswift
import WidgetKit
import SwiftUI

// Entry die de widgetstatus op een bepaald moment vertegenwoordigt
struct TaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Laadstatus voor visuele feedback
    var isLoading: Bool = false
}

// Gegevensmodel gedeeld tussen app en widget
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var isCompleted: Bool
    var priority: Priority

    enum Priority: String, Codable {
        case low, medium, high
    }
}

struct TaskTimelineProvider: TimelineProvider {
    // Placeholder weergegeven tijdens initieel laden
    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(
            date: Date(),
            tasks: [
                Task(id: UUID(), title: "Voorbeeldtaak", isCompleted: false, priority: .medium)
            ]
        )
    }

    // Snapshot voor de widgetgalerij
    func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
        let entry = TaskEntry(
            date: Date(),
            tasks: TaskDataManager.shared.fetchTasks().prefix(3).map { $0 }
        )
        completion(entry)
    }

    // Volledige timeline met vernieuwingsbeleid
    func getTimeline(in context: Context, completion: @escaping (Timeline<TaskEntry>) -> Void) {
        let tasks = TaskDataManager.shared.fetchTasks()
        let entry = TaskEntry(date: Date(), tasks: Array(tasks.prefix(3)))

        // Vernieuwing over 15 minuten of na een gebruikersactie
        let nextUpdate = Calendar.current.date(
            byAdding: .minute,
            value: 15,
            to: Date()
        ) ?? Date()

        let timeline = Timeline(
            entries: [entry],
            policy: .after(nextUpdate)
        )
        completion(timeline)
    }
}

De provider gebruikt een gedeelde TaskDataManager om toegang te krijgen tot de gegevens. Deze aanpak garandeert synchronisatie tussen de hoofdapplicatie en de widget.

App Group verplicht

Om gegevens te delen tussen app en widget moet een App Group worden geconfigureerd in de project capabilities. UserDefaults of bestanden moeten deze gedeelde groep gebruiken.

De Manager voor Gedeelde Gegevens

Het delen van gegevens tussen de applicatie en de widget vereist een gemeenschappelijke container die toegankelijk is via App Group.

TaskDataManager.swiftswift
import Foundation

final class TaskDataManager {
    // Singleton voor globale toegang
    static let shared = TaskDataManager()

    // App Group identifier geconfigureerd in Xcode
    private let appGroupID = "group.com.example.taskapp"

    // UserDefaults gedeeld tussen app en widget
    private var sharedDefaults: UserDefaults? {
        UserDefaults(suiteName: appGroupID)
    }

    private let tasksKey = "tasks"

    private init() {}

    // Haalt taken op uit gedeelde opslag
    func fetchTasks() -> [Task] {
        guard let data = sharedDefaults?.data(forKey: tasksKey),
              let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
            return []
        }
        return tasks
    }

    // Opslaan met widgetmelding
    func saveTasks(_ tasks: [Task]) {
        guard let data = try? JSONEncoder().encode(tasks) else { return }
        sharedDefaults?.set(data, forKey: tasksKey)
    }

    // Werkt een specifieke taak bij
    func updateTask(_ task: Task) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == task.id }) {
            tasks[index] = task
            saveTasks(tasks)
        }
    }

    // Wisselt voltooiingsstatus om
    func toggleTaskCompletion(taskID: UUID) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == taskID }) {
            tasks[index].isCompleted.toggle()
            saveTasks(tasks)
        }
    }
}

Deze manager kapselt alle persistentielogica in en zal worden gebruikt door zowel de applicatie als de App Intents van de widget.

Aanmaken van het App Intent voor Interactiviteit

Het App Intent definieert de actie die wordt uitgevoerd wanneer de gebruiker met de widget interageert. iOS voert deze actie op de achtergrond uit en vernieuwt vervolgens automatisch de widget.

ToggleTaskIntent.swiftswift
import AppIntents
import WidgetKit

// Intent om de taakstatus om te wisselen
struct ToggleTaskIntent: AppIntent {
    // Titel weergegeven in Siri-snelkoppelingen
    static var title: LocalizedStringResource = "Taakstatus omwisselen"

    // Beschrijving voor toegankelijkheid
    static var description = IntentDescription("Markeert een taak als voltooid of niet voltooid.")

    // Parameter: ID van de te wijzigen taak
    @Parameter(title: "Taak-ID")
    var taskID: String

    // Vereiste initializer voor AppIntent
    init() {}

    // Initializer met parameter voor aanmaak vanuit de view
    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // Uitvoering van de actie
    func perform() async throws -> some IntentResult {
        // Conversie van string-ID naar UUID
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        // Update van de taak
        TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)

        // Vraagt widget-vernieuwing aan
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

De oproep naar WidgetCenter.shared.reloadTimelines veroorzaakt een onmiddellijke widget-vernieuwing na de actie en garandeert directe visuele feedback.

Klaar om je iOS gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Widget-View met Interactieve Knoppen

De widget-view gebruikt het standaard SwiftUI Button component met de intent als actie. iOS 17+ onderschept deze interacties automatisch om het App Intent uit te voeren.

TaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct TaskWidgetView: View {
    let entry: TaskEntry

    // Aanpassing aan widgetgrootte
    @Environment(\.widgetFamily) var family

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Header met titel en teller
            headerView

            // Takenlijst met interactieve knoppen
            ForEach(entry.tasks.prefix(tasksLimit)) { task in
                TaskRowView(task: task)
            }

            Spacer(minLength: 0)
        }
        .padding()
    }

    // Aantal taken volgens grootte
    private var tasksLimit: Int {
        switch family {
        case .systemSmall: return 2
        case .systemMedium: return 3
        default: return 4
        }
    }

    private var headerView: some View {
        HStack {
            Text("Taken")
                .font(.headline)
                .fontWeight(.bold)

            Spacer()

            // Badge met aantal resterende taken
            let remaining = entry.tasks.filter { !$0.isCompleted }.count
            Text("\(remaining)")
                .font(.caption.bold())
                .foregroundStyle(.white)
                .padding(.horizontal, 8)
                .padding(.vertical, 4)
                .background(remaining > 0 ? Color.orange : Color.green)
                .clipShape(Capsule())
        }
    }
}

struct TaskRowView: View {
    let task: Task

    var body: some View {
        // Knop met App Intent als actie
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Voltooiingsindicator
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

                // Taaktitel
                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)
                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
                    .lineLimit(1)

                Spacer()

                // Prioriteitsindicator
                priorityIndicator
            }
            .padding(.vertical, 6)
            .padding(.horizontal, 10)
            .background(Color(.systemBackground).opacity(0.5))
            .cornerRadius(8)
        }
        .buttonStyle(.plain)
    }

    @ViewBuilder
    private var priorityIndicator: some View {
        switch task.priority {
        case .high:
            Image(systemName: "exclamationmark.circle.fill")
                .foregroundStyle(.red)
        case .medium:
            Image(systemName: "minus.circle.fill")
                .foregroundStyle(.orange)
        case .low:
            EmptyView()
        }
    }
}

De Button(intent:) syntax verbindt de knop rechtstreeks met het App Intent. Bij aanraking voert iOS perform() uit en vernieuwt vervolgens automatisch de widget.

Interactieve Toggle voor Widgets

Voor aan/uit-acties biedt het Toggle component een alternatief voor de knop met een native iOS-stijl.

ToggleWidgetView.swiftswift
import SwiftUI
import AppIntents

// Specifiek intent voor Toggle met expliciete status
struct SetTaskCompletionIntent: AppIntent {
    static var title: LocalizedStringResource = "Taakstatus instellen"

    @Parameter(title: "Taak-ID")
    var taskID: String

    // Doelstatus: true = voltooid, false = niet voltooid
    @Parameter(title: "Voltooid")
    var isCompleted: Bool

    init() {}

    init(taskID: UUID, isCompleted: Bool) {
        self.taskID = taskID.uuidString
        self.isCompleted = isCompleted
    }

    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        var tasks = TaskDataManager.shared.fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == uuid }) {
            // Stelt status expliciet in (geen toggle)
            tasks[index].isCompleted = isCompleted
            TaskDataManager.shared.saveTasks(tasks)
        }

        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
        return .result()
    }
}

struct TaskToggleRowView: View {
    let task: Task

    var body: some View {
        HStack {
            Text(task.title)
                .font(.subheadline)
                .strikethrough(task.isCompleted)

            Spacer()

            // Interactieve toggle met intent
            Toggle(
                isOn: task.isCompleted,
                intent: SetTaskCompletionIntent(
                    taskID: task.id,
                    isCompleted: !task.isCompleted
                )
            )
            .toggleStyle(.switch)
            .labelsHidden()
        }
        .padding(.vertical, 4)
    }
}

Toggle biedt een intuïtievere interactie voor binaire statussen en integreert natuurlijk in iOS-ontwerpen.

Beperkingen van interactieve widgets

Widgets kunnen geen alerts, sheets of navigatie weergeven. Alle acties moeten autonoom zijn en de zichtbare status direct bijwerken.

Vernieuwingsanimaties en Overgangen

iOS 17+ maakt het mogelijk om overgangen te animeren tijdens widget-vernieuwing na een actie. De .contentTransition modifier regelt deze animaties.

AnimatedTaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct AnimatedTaskRowView: View {
    let task: Task

    var body: some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Icoon met overgangsanimatie
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)
                    // Icoonanimatie bij wijziging
                    .contentTransition(.symbolEffect(.replace))

                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)
                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
                    // Tekstanimatie
                    .contentTransition(.opacity)

                Spacer()
            }
            .padding(.vertical, 6)
            .padding(.horizontal, 10)
            .background(
                RoundedRectangle(cornerRadius: 8)
                    .fill(task.isCompleted ? Color.green.opacity(0.1) : Color.clear)
            )
            // Achtergrondanimatie
            .animation(.easeInOut(duration: 0.3), value: task.isCompleted)
        }
        .buttonStyle(.plain)
    }
}

// Widget met geanimeerde invalidatie
struct AnimatedTaskWidget: Widget {
    let kind: String = "AnimatedTaskWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            AnimatedTaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Geanimeerde Taken")
        .description("Widgets met vloeiende animaties.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
        // Activering van inhoudsanimaties
        .contentMarginsDisabled()
    }
}

struct AnimatedTaskWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            headerView

            ForEach(entry.tasks) { task in
                AnimatedTaskRowView(task: task)
            }

            Spacer(minLength: 0)
        }
        .padding()
    }

    private var headerView: some View {
        HStack {
            Text("Taken")
                .font(.headline.bold())

            Spacer()

            let completed = entry.tasks.filter(\.isCompleted).count
            let total = entry.tasks.count

            // Geanimeerde voortgang
            Text("\(completed)/\(total)")
                .font(.caption.bold())
                .foregroundStyle(.secondary)
                .contentTransition(.numericText())
        }
    }
}

De animaties .symbolEffect(.replace) en .numericText() creëren vloeiende overgangen tussen statussen en verbeteren de gebruikerservaring aanzienlijk.

Configureerbare Widget met AppIntentConfiguration

Voor door gebruikers aanpasbare widgets (filters, categorieën) vervangt AppIntentConfiguration de StaticConfiguration.

ConfigurableTaskWidget.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Configuratie blootgesteld aan de gebruiker
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Taakconfiguratie"
    static var description = IntentDescription("Pas de taakweergave aan.")

    // Filter op prioriteit
    @Parameter(title: "Prioriteit", default: .all)
    var priorityFilter: PriorityFilter

    // Voltooide taken weergeven
    @Parameter(title: "Voltooide weergeven", default: true)
    var showCompleted: Bool

    // Maximaal aantal taken
    @Parameter(title: "Aantal taken", default: 3)
    var maxTasks: Int
}

// Enum voor prioriteitsfilter
enum PriorityFilter: String, AppEnum {
    case all
    case high
    case medium
    case low

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioriteit"

    static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
        .all: "Alle",
        .high: "Hoog",
        .medium: "Gemiddeld",
        .low: "Laag"
    ]
}

// Provider aangepast aan de configuratie
struct ConfigurableTaskProvider: AppIntentTimelineProvider {
    typealias Entry = TaskEntry
    typealias Intent = TaskWidgetConfigurationIntent

    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(date: Date(), tasks: [])
    }

    func snapshot(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> TaskEntry {
        let tasks = filteredTasks(for: configuration)
        return TaskEntry(date: Date(), tasks: tasks)
    }

    func timeline(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> Timeline<TaskEntry> {
        let tasks = filteredTasks(for: configuration)
        let entry = TaskEntry(date: Date(), tasks: tasks)

        let nextUpdate = Date().addingTimeInterval(15 * 60)
        return Timeline(entries: [entry], policy: .after(nextUpdate))
    }

    // Past configuratiefilters toe
    private func filteredTasks(for config: TaskWidgetConfigurationIntent) -> [Task] {
        var tasks = TaskDataManager.shared.fetchTasks()

        // Filtering op prioriteit
        if config.priorityFilter != .all {
            let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
            tasks = tasks.filter { $0.priority == priority }
        }

        // Filtering van voltooide indien nodig
        if !config.showCompleted {
            tasks = tasks.filter { !$0.isCompleted }
        }

        // Beperking van het aantal
        return Array(tasks.prefix(config.maxTasks))
    }
}

// Widget met gebruikersconfiguratie
struct ConfigurableTaskWidget: Widget {
    let kind: String = "ConfigurableTaskWidget"

    var body: some WidgetConfiguration {
        // AppIntentConfiguration voor configureerbare widgets
        AppIntentConfiguration(
            kind: kind,
            intent: TaskWidgetConfigurationIntent.self,
            provider: ConfigurableTaskProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Aangepaste Taken")
        .description("Filter en pas uw taken aan.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

De gebruiker kan de widget nu configureren door lang in te drukken en zo een gepersonaliseerde ervaring krijgen zonder extra code in de applicatie.

Foutafhandeling en Laadstatussen

Een goede UX vereist het afhandelen van foutgevallen en tussentoestanden tijdens interacties.

TaskIntentWithFeedback.swiftswift
import AppIntents
import WidgetKit

struct ToggleTaskWithFeedbackIntent: AppIntent {
    static var title: LocalizedStringResource = "Taak omwisselen met feedback"

    @Parameter(title: "Taak-ID")
    var taskID: String

    init() {}

    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
        guard let uuid = UUID(uuidString: taskID) else {
            // Stille fout retourneren
            return .result(value: false)
        }

        // Simuleert async operatie (bijv. server-sync)
        do {
            try await Task.sleep(for: .milliseconds(100))

            TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)
            WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

            return .result(value: true)
        } catch {
            // Fout: widget niet bijwerken
            return .result(value: false)
        }
    }
}

// View met laadstatus
struct TaskRowWithLoadingView: View {
    let task: Task
    @State private var isLoading = false

    var body: some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Conditionele indicator
                Group {
                    if isLoading {
                        ProgressView()
                            .scaleEffect(0.8)
                    } else {
                        Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                            .foregroundStyle(task.isCompleted ? .green : .secondary)
                    }
                }
                .frame(width: 24, height: 24)

                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)

                Spacer()
            }
            .padding(.vertical, 6)
            .padding(.horizontal, 10)
            .background(Color(.systemBackground).opacity(0.5))
            .cornerRadius(8)
        }
        .buttonStyle(.plain)
        .disabled(isLoading)
        .opacity(isLoading ? 0.6 : 1.0)
    }
}

Onmiddellijke visuele feedback (verminderde opaciteit, laadindicator) informeert de gebruiker dat zijn actie is geregistreerd.

Best Practices en Optimalisaties

Verschillende patronen garanderen prestatieve en betrouwbare interactieve widgets.

WidgetBestPractices.swiftswift
import WidgetKit
import SwiftUI

// 1. Cache altijd ongeldig maken na wijziging
final class WidgetRefreshManager {
    static func refreshAllWidgets() {
        // Vernieuwing van alle app-widgets
        WidgetCenter.shared.reloadAllTimelines()
    }

    static func refreshWidget(kind: String) {
        // Vernieuwing van een specifieke widget
        WidgetCenter.shared.reloadTimelines(ofKind: kind)
    }

    // Oproep vanuit de app na gegevenswijziging
    static func notifyDataChanged() {
        Task { @MainActor in
            refreshAllWidgets()
        }
    }
}

// 2. Beperk view-complexiteit
struct OptimizedWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        // Eenvoudige views zonder GeometryReader prefereren
        VStack(alignment: .leading, spacing: 8) {
            ForEach(entry.tasks.prefix(3)) { task in
                // Lichtgewicht componenten
                minimalTaskRow(task)
            }
        }
        .padding()
    }

    // Minimale view voor prestaties
    @ViewBuilder
    private func minimalTaskRow(_ task: Task) -> some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack {
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                Text(task.title)
                    .lineLimit(1)
            }
        }
        .buttonStyle(.plain)
    }
}

// 3. Gebruik @AppStorage voor eenvoudige statussen
struct QuickSettingsWidgetView: View {
    // Directe toegang tot gedeelde UserDefaults
    @AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
    private var showCompleted = true

    var body: some View {
        // Status blijft behouden tussen vernieuwingen
        Text(showCompleted ? "Alle weergeven" : "Voltooide verbergen")
    }
}

// 4. Pre-bereken gegevens in de provider
struct OptimizedTaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Voorberekende gegevens
    let completedCount: Int
    let pendingCount: Int
    let highPriorityCount: Int

    init(date: Date, tasks: [Task]) {
        self.date = date
        self.tasks = tasks

        // Berekeningen één keer uitgevoerd
        self.completedCount = tasks.filter(\.isCompleted).count
        self.pendingCount = tasks.filter { !$0.isCompleted }.count
        self.highPriorityCount = tasks.filter { $0.priority == .high && !$0.isCompleted }.count
    }
}

Deze optimalisaties zorgen voor responsieve widgets die de batterij niet overmatig verbruiken.

Widgets debuggen

Gebruik het widget-schema in Xcode voor debugging. Het canvas-voorbeeld maakt het mogelijk om verschillende groottes en statussen te testen zonder installatie op het apparaat.

Conclusie

WidgetKit iOS 17+ met App Intents transformeert widgets in echte interactieve uitbreidingen van iOS-applicaties. Deze declaratieve architectuur vereenvoudigt de ontwikkeling aanzienlijk en biedt een native, vloeiende gebruikerservaring.

Checklist Interactieve iOS 17+ Widget

  • ✅ App Group configureren voor gegevensuitwisseling
  • ✅ Timeline Provider aanmaken met geschikte vernieuwing
  • ✅ App Intents implementeren voor elke actie
  • Button(intent:) of Toggle(intent:) gebruiken voor interactiviteit
  • WidgetCenter.shared.reloadTimelines aanroepen na wijziging
  • ✅ Verplichte .containerBackground toevoegen voor iOS 17+
  • ✅ Vloeiende overgangsanimaties implementeren
  • ✅ Laad- en foutstatussen afhandelen
  • ✅ Views optimaliseren voor batterijprestaties
  • ✅ Testen op alle ondersteunde widgetgroottes

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

#widgetkit
#ios
#app-intents
#swift
#widgets

Delen

Gerelateerde artikelen