WidgetKit iOS 17+: Interaktive Widgets mit App Intents

Vollständige Anleitung zur Erstellung interaktiver iOS-Widgets mit WidgetKit und App Intents. Buttons, Toggles, Animationen und Best Practices für iOS 17+ in 2026.

WidgetKit iOS 17+ mit interaktiven Widgets und App Intents für moderne iOS-Anwendungen

iOS 17 hat WidgetKit durch die Einführung nativer Interaktivität revolutioniert. Widgets sind keine statischen Anzeigen mehr: Sie können nun direkt vom Home-Bildschirm aus auf Benutzeraktionen reagieren, ohne dass die App geöffnet werden muss. Diese wichtige Weiterentwicklung basiert auf dem App Intents Framework und bietet eine flüssige, moderne Benutzererfahrung.

Was dieser Artikel behandelt

Dieser Artikel zeigt die vollständige Erstellung interaktiver iOS 17+ Widgets, von der Projektkonfiguration bis zu fortgeschrittenen Mustern mit Animationen und Zustandsverwaltung.

Architektur Interaktiver Widgets

Die Interaktivität der iOS 17+ Widgets funktioniert über das App Intents Framework. Im Gegensatz zu traditionellen Deep Links, die die App öffnen würden, ermöglichen App Intents die Code-Ausführung direkt vom Widget aus und aktualisieren anschließend automatisch die Anzeige mit den neuen Daten.

InteractiveWidgetArchitecture.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Die Architektur basiert auf drei Hauptkomponenten:
// 1. Widget Timeline Provider - liefert die Daten
// 2. Widget View - zeigt das Interface mit Button/Toggle
// 3. App Intent - führt die Aktion bei Berührung aus

struct TaskWidget: Widget {
    // Eindeutige Widget-Kennung
    let kind: String = "TaskWidget"

    var body: some WidgetConfiguration {
        // StaticConfiguration für Widgets ohne Parameter
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                // Erforderlich für App Intents
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Aufgaben")
        .description("Verwalten Sie Ihre Aufgaben vom Home-Bildschirm aus.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

Das Widget deklariert seine Konfiguration und gibt den Provider an, der die Daten liefert. Das Attribut .containerBackground ist seit iOS 17 für interaktive Widgets verpflichtend.

Erstellung des Timeline Providers

Der Timeline Provider bestimmt, wann und wie das Widget aktualisiert wird. Bei interaktiven Widgets muss er auch auf Änderungen reagieren, die durch App Intents ausgelöst werden.

TaskTimelineProvider.swiftswift
import WidgetKit
import SwiftUI

// Entry repräsentiert den Widget-Zustand zu einem bestimmten Zeitpunkt
struct TaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Ladezustand für visuelles Feedback
    var isLoading: Bool = false
}

// Datenmodell, das zwischen App und Widget geteilt wird
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 während des initialen Ladens
    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(
            date: Date(),
            tasks: [
                Task(id: UUID(), title: "Beispielaufgabe", isCompleted: false, priority: .medium)
            ]
        )
    }

    // Snapshot für die Widget-Galerie
    func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
        let entry = TaskEntry(
            date: Date(),
            tasks: TaskDataManager.shared.fetchTasks().prefix(3).map { $0 }
        )
        completion(entry)
    }

    // Vollständige Timeline mit Refresh-Policy
    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)))

        // Aktualisierung in 15 Minuten oder nach Benutzeraktion
        let nextUpdate = Calendar.current.date(
            byAdding: .minute,
            value: 15,
            to: Date()
        ) ?? Date()

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

Der Provider verwendet einen gemeinsamen TaskDataManager für den Datenzugriff. Dieser Ansatz garantiert die Synchronisation zwischen Hauptanwendung und Widget.

App Group erforderlich

Um Daten zwischen App und Widget zu teilen, muss eine App Group in den Projekt-Capabilities konfiguriert werden. UserDefaults oder Dateien müssen diese gemeinsame Gruppe nutzen.

Der Manager für Geteilte Daten

Der Datenaustausch zwischen Anwendung und Widget erfordert einen gemeinsamen Container, der über App Group zugänglich ist.

TaskDataManager.swiftswift
import Foundation

final class TaskDataManager {
    // Singleton für globalen Zugriff
    static let shared = TaskDataManager()

    // App Group Bezeichner, in Xcode konfiguriert
    private let appGroupID = "group.com.example.taskapp"

    // UserDefaults zwischen App und Widget geteilt
    private var sharedDefaults: UserDefaults? {
        UserDefaults(suiteName: appGroupID)
    }

    private let tasksKey = "tasks"

    private init() {}

    // Aufgaben aus dem geteilten Speicher abrufen
    func fetchTasks() -> [Task] {
        guard let data = sharedDefaults?.data(forKey: tasksKey),
              let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
            return []
        }
        return tasks
    }

    // Speichern mit Widget-Benachrichtigung
    func saveTasks(_ tasks: [Task]) {
        guard let data = try? JSONEncoder().encode(tasks) else { return }
        sharedDefaults?.set(data, forKey: tasksKey)
    }

    // Spezifische Aufgabe aktualisieren
    func updateTask(_ task: Task) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == task.id }) {
            tasks[index] = task
            saveTasks(tasks)
        }
    }

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

Dieser Manager kapselt die gesamte Persistenzlogik und wird sowohl von der Anwendung als auch von den Widget App Intents verwendet.

Erstellung des App Intent für Interaktivität

Der App Intent definiert die Aktion, die bei Benutzerinteraktion mit dem Widget ausgeführt wird. iOS führt diese Aktion im Hintergrund aus und aktualisiert das Widget anschließend automatisch.

ToggleTaskIntent.swiftswift
import AppIntents
import WidgetKit

// Intent zum Umschalten des Aufgabenstatus
struct ToggleTaskIntent: AppIntent {
    // Titel in Siri-Kurzbefehlen
    static var title: LocalizedStringResource = "Aufgabenstatus umschalten"

    // Beschreibung für Barrierefreiheit
    static var description = IntentDescription("Markiert eine Aufgabe als erledigt oder nicht erledigt.")

    // Parameter: ID der zu ändernden Aufgabe
    @Parameter(title: "Aufgaben-ID")
    var taskID: String

    // Erforderlicher Initializer für AppIntent
    init() {}

    // Initializer mit Parameter zur Erstellung aus der View
    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // Ausführung der Aktion
    func perform() async throws -> some IntentResult {
        // Konvertierung der String-ID in UUID
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        // Aktualisierung der Aufgabe
        TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)

        // Widget-Aktualisierung anfordern
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

Der Aufruf von WidgetCenter.shared.reloadTimelines löst eine sofortige Widget-Aktualisierung nach der Aktion aus und gewährleistet ein unmittelbares visuelles Feedback.

Bereit für deine iOS-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Widget-View mit Interaktiven Buttons

Die Widget-View verwendet die Standard-SwiftUI-Komponente Button mit dem Intent als Aktion. iOS 17+ fängt diese Interaktionen automatisch ab, um den App Intent auszuführen.

TaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct TaskWidgetView: View {
    let entry: TaskEntry

    // Anpassung an die Widget-Größe
    @Environment(\.widgetFamily) var family

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Header mit Titel und Zähler
            headerView

            // Aufgabenliste mit interaktiven Buttons
            ForEach(entry.tasks.prefix(tasksLimit)) { task in
                TaskRowView(task: task)
            }

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

    // Anzahl der Aufgaben je nach Größe
    private var tasksLimit: Int {
        switch family {
        case .systemSmall: return 2
        case .systemMedium: return 3
        default: return 4
        }
    }

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

            Spacer()

            // Badge mit der Anzahl verbleibender Aufgaben
            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 {
        // Button mit App Intent als Aktion
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Erledigt-Indikator
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

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

                Spacer()

                // Prioritätsindikator
                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()
        }
    }
}

Die Syntax Button(intent:) verbindet den Button direkt mit dem App Intent. Bei Berührung führt iOS perform() aus und aktualisiert das Widget anschließend automatisch.

Interaktiver Toggle für Widgets

Für Aktionen vom Typ Ein/Aus bietet die Toggle-Komponente eine Alternative zum Button mit nativem iOS-Stil.

ToggleWidgetView.swiftswift
import SwiftUI
import AppIntents

// Spezifischer Intent für Toggle mit explizitem Status
struct SetTaskCompletionIntent: AppIntent {
    static var title: LocalizedStringResource = "Aufgabenstatus festlegen"

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

    // Zielstatus: true = erledigt, false = nicht erledigt
    @Parameter(title: "Erledigt")
    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 }) {
            // Status explizit setzen (nicht umschalten)
            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()

            // Interaktiver Toggle mit Intent
            Toggle(
                isOn: task.isCompleted,
                intent: SetTaskCompletionIntent(
                    taskID: task.id,
                    isCompleted: !task.isCompleted
                )
            )
            .toggleStyle(.switch)
            .labelsHidden()
        }
        .padding(.vertical, 4)
    }
}

Der Toggle bietet eine intuitivere Interaktion für binäre Zustände und integriert sich nahtlos in iOS-Designs.

Einschränkungen interaktiver Widgets

Widgets können keine Alerts, Sheets oder Navigation anzeigen. Alle Aktionen müssen autonom sein und den sichtbaren Status direkt aktualisieren.

Refresh-Animationen und Übergänge

iOS 17+ ermöglicht die Animation von Übergängen während der Widget-Aktualisierung nach einer Aktion. Der Modifier .contentTransition steuert diese Animationen.

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) {
                // Icon mit Übergangsanimation
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)
                    // Icon-Animation bei Änderung
                    .contentTransition(.symbolEffect(.replace))

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

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

// Widget mit animierter Invalidierung
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("Animierte Aufgaben")
        .description("Widgets mit weichen Animationen.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
        // Aktivierung der Inhaltsanimationen
        .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("Aufgaben")
                .font(.headline.bold())

            Spacer()

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

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

Die Animationen .symbolEffect(.replace) und .numericText() erzeugen weiche Übergänge zwischen Zuständen und verbessern die Benutzererfahrung erheblich.

Konfigurierbares Widget mit AppIntentConfiguration

Für vom Benutzer anpassbare Widgets (Filter, Kategorien) ersetzt AppIntentConfiguration die StaticConfiguration.

ConfigurableTaskWidget.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Dem Benutzer zugängliche Konfiguration
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Aufgabenkonfiguration"
    static var description = IntentDescription("Passen Sie die Aufgabenanzeige an.")

    // Filter nach Priorität
    @Parameter(title: "Priorität", default: .all)
    var priorityFilter: PriorityFilter

    // Erledigte Aufgaben anzeigen
    @Parameter(title: "Erledigte anzeigen", default: true)
    var showCompleted: Bool

    // Maximale Anzahl von Aufgaben
    @Parameter(title: "Anzahl Aufgaben", default: 3)
    var maxTasks: Int
}

// Enum für Prioritätsfilter
enum PriorityFilter: String, AppEnum {
    case all
    case high
    case medium
    case low

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priorität"

    static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
        .all: "Alle",
        .high: "Hoch",
        .medium: "Mittel",
        .low: "Niedrig"
    ]
}

// An die Konfiguration angepasster Provider
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))
    }

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

        // Filterung nach Priorität
        if config.priorityFilter != .all {
            let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
            tasks = tasks.filter { $0.priority == priority }
        }

        // Filterung der Erledigten bei Bedarf
        if !config.showCompleted {
            tasks = tasks.filter { !$0.isCompleted }
        }

        // Anzahl begrenzen
        return Array(tasks.prefix(config.maxTasks))
    }
}

// Widget mit Benutzerkonfiguration
struct ConfigurableTaskWidget: Widget {
    let kind: String = "ConfigurableTaskWidget"

    var body: some WidgetConfiguration {
        // AppIntentConfiguration für konfigurierbare Widgets
        AppIntentConfiguration(
            kind: kind,
            intent: TaskWidgetConfigurationIntent.self,
            provider: ConfigurableTaskProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Personalisierte Aufgaben")
        .description("Filtern und personalisieren Sie Ihre Aufgaben.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

Der Benutzer kann das Widget nun durch langes Drücken konfigurieren und erhält so ein personalisiertes Erlebnis ohne zusätzlichen Code in der Anwendung.

Fehlerbehandlung und Ladezustände

Eine gute UX erfordert die Behandlung von Fehlerfällen und Zwischenzuständen während der Interaktionen.

TaskIntentWithFeedback.swiftswift
import AppIntents
import WidgetKit

struct ToggleTaskWithFeedbackIntent: AppIntent {
    static var title: LocalizedStringResource = "Aufgabe mit Feedback umschalten"

    @Parameter(title: "Aufgaben-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 {
            // Stiller Fehler zurückgeben
            return .result(value: false)
        }

        // Asynchrone Operation simulieren (z.B. 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 {
            // Fehler: Widget nicht aktualisieren
            return .result(value: false)
        }
    }
}

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

    var body: some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Bedingter Indikator
                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)
    }
}

Unmittelbares visuelles Feedback (verringerte Deckkraft, Ladeindikator) signalisiert dem Benutzer, dass seine Aktion registriert wurde.

Best Practices und Optimierungen

Mehrere Muster gewährleisten leistungsstarke und zuverlässige interaktive Widgets.

WidgetBestPractices.swiftswift
import WidgetKit
import SwiftUI

// 1. Cache nach jeder Änderung immer invalidieren
final class WidgetRefreshManager {
    static func refreshAllWidgets() {
        // Aktualisierung aller App-Widgets
        WidgetCenter.shared.reloadAllTimelines()
    }

    static func refreshWidget(kind: String) {
        // Aktualisierung eines bestimmten Widgets
        WidgetCenter.shared.reloadTimelines(ofKind: kind)
    }

    // Aufruf aus der App nach Datenänderung
    static func notifyDataChanged() {
        Task { @MainActor in
            refreshAllWidgets()
        }
    }
}

// 2. Komplexität der Views begrenzen
struct OptimizedWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        // Einfache Views ohne GeometryReader bevorzugen
        VStack(alignment: .leading, spacing: 8) {
            ForEach(entry.tasks.prefix(3)) { task in
                // Leichtgewichtige Komponenten
                minimalTaskRow(task)
            }
        }
        .padding()
    }

    // Minimale View für Performance
    @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. @AppStorage für einfache Zustände verwenden
struct QuickSettingsWidgetView: View {
    // Direkter Zugriff auf geteilte UserDefaults
    @AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
    private var showCompleted = true

    var body: some View {
        // Status bleibt zwischen Aktualisierungen erhalten
        Text(showCompleted ? "Alle anzeigen" : "Erledigte ausblenden")
    }
}

// 4. Daten im Provider vorberechnen
struct OptimizedTaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Vorberechnete Daten
    let completedCount: Int
    let pendingCount: Int
    let highPriorityCount: Int

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

        // Berechnungen werden nur einmal durchgeführt
        self.completedCount = tasks.filter(\.isCompleted).count
        self.pendingCount = tasks.filter { !$0.isCompleted }.count
        self.highPriorityCount = tasks.filter { $0.priority == .high && !$0.isCompleted }.count
    }
}

Diese Optimierungen sorgen für reaktionsschnelle Widgets, die den Akku nicht übermäßig belasten.

Widget-Debugging

Verwenden Sie das Widget-Schema in Xcode für das Debugging. Die Canvas-Vorschau ermöglicht das Testen verschiedener Größen und Zustände ohne Geräteinstallation.

Fazit

WidgetKit iOS 17+ mit App Intents verwandelt Widgets in echte interaktive Erweiterungen von iOS-Anwendungen. Diese deklarative Architektur vereinfacht die Entwicklung erheblich und bietet eine native, flüssige Benutzererfahrung.

Checkliste Interaktives iOS 17+ Widget

  • ✅ App Group für Datenaustausch konfigurieren
  • ✅ Timeline Provider mit angemessener Aktualisierung erstellen
  • ✅ App Intents für jede Aktion implementieren
  • Button(intent:) oder Toggle(intent:) für Interaktivität verwenden
  • WidgetCenter.shared.reloadTimelines nach jeder Änderung aufrufen
  • ✅ Pflicht-.containerBackground für iOS 17+ hinzufügen
  • ✅ Weiche Übergangsanimationen implementieren
  • ✅ Lade- und Fehlerzustände behandeln
  • ✅ Views für Akkuleistung optimieren
  • ✅ Auf allen unterstützten Widget-Größen testen

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Tags

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

Teilen

Verwandte Artikel