WidgetKit iOS 17-26: Interaktive Widgets mit App Intents
Vollständige Anleitung zur Erstellung interaktiver iOS-Widgets mit WidgetKit und App Intents. Buttons, Toggles, Animationen, iOS 26 Liquid Glass Material und WWDC 2026 Updates.

iOS 17 führte native Interaktivität in WidgetKit ein und ermöglicht Widgets, direkt vom Home-Bildschirm auf Benutzeraktionen zu reagieren, ohne die App zu öffnen. iOS 26 verfeinerte dies mit Liquid Glass Material und akzentuierten Rendering-Modi. Diese Anleitung behandelt die Entwicklung interaktiver Widgets von iOS 17 bis iOS 26.
Erstellung interaktiver iOS 17-26 Widgets, von der Projektkonfiguration bis zu fortgeschrittenen Mustern mit Animationen, Zustandsverwaltung und den neuesten WWDC 2026 App Intents Funktionen.
Architektur Interaktiver Widgets in iOS 17+
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.
import WidgetKit
import SwiftUI
import AppIntents
// The architecture relies on three main components:
// 1. Widget Timeline Provider - supplies the data
// 2. Widget View - displays the interface with Button/Toggle
// 3. App Intent - executes the action on tap
struct TaskWidget: Widget {
// Unique widget identifier
let kind: String = "TaskWidget"
var body: some WidgetConfiguration {
// StaticConfiguration for widgets without parameters
StaticConfiguration(
kind: kind,
provider: TaskTimelineProvider()
) { entry in
TaskWidgetView(entry: entry)
// Required for App Intents since iOS 17
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Tasks")
.description("Manage your tasks from the home screen.")
.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, und iOS 26 wendet automatisch Liquid Glass Styling an, wenn angemessen.
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.
import WidgetKit
import SwiftUI
// Entry representing widget state at a given moment
struct TaskEntry: TimelineEntry {
let date: Date
let tasks: [Task]
// Loading state for visual feedback
var isLoading: Bool = false
}
// Data model shared between app and 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 displayed during initial loading
func placeholder(in context: Context) -> TaskEntry {
TaskEntry(
date: Date(),
tasks: [
Task(id: UUID(), title: "Sample task", isCompleted: false, priority: .medium)
]
)
}
// Snapshot for widget gallery
func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
let entry = TaskEntry(
date: Date(),
tasks: TaskDataManager.shared.fetchTasks().prefix(3).map { $0 }
)
completion(entry)
}
// Complete timeline with 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)))
// Refresh in 15 minutes or after user action
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. Für verwandte Zustandsverwaltungsmuster siehe SwiftUI @Observable vs @State.
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.
import Foundation
final class TaskDataManager {
// Singleton for global access
static let shared = TaskDataManager()
// App Group identifier configured in Xcode
private let appGroupID = "group.com.example.taskapp"
// UserDefaults shared between app and widget
private var sharedDefaults: UserDefaults? {
UserDefaults(suiteName: appGroupID)
}
private let tasksKey = "tasks"
private init() {}
// Fetch tasks from shared storage
func fetchTasks() -> [Task] {
guard let data = sharedDefaults?.data(forKey: tasksKey),
let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
return []
}
return tasks
}
// Save with widget notification
func saveTasks(_ tasks: [Task]) {
guard let data = try? JSONEncoder().encode(tasks) else { return }
sharedDefaults?.set(data, forKey: tasksKey)
}
// Update a specific task
func updateTask(_ task: Task) {
var tasks = fetchTasks()
if let index = tasks.firstIndex(where: { $0.id == task.id }) {
tasks[index] = task
saveTasks(tasks)
}
}
// Toggle completed state
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.
import AppIntents
import WidgetKit
// Intent to toggle task state
struct ToggleTaskIntent: AppIntent {
// Title displayed in Siri shortcuts
static var title: LocalizedStringResource = "Toggle task state"
// Description for accessibility
static var description = IntentDescription("Marks a task as completed or not completed.")
// Parameter: ID of task to modify
@Parameter(title: "Task ID")
var taskID: String
// Required initializer for AppIntent
init() {}
// Initializer with parameter for view creation
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// Action execution
func perform() async throws -> some IntentResult {
// Convert string ID to UUID
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
// Update the task
TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)
// Request widget refresh
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.
import SwiftUI
import WidgetKit
struct TaskWidgetView: View {
let entry: TaskEntry
// Adapt to widget size
@Environment(\.widgetFamily) var family
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// Header with title and counter
headerView
// Task list with interactive buttons
ForEach(entry.tasks.prefix(tasksLimit)) { task in
TaskRowView(task: task)
}
Spacer(minLength: 0)
}
.padding()
}
// Number of tasks based on size
private var tasksLimit: Int {
switch family {
case .systemSmall: return 2
case .systemMedium: return 3
default: return 4
}
}
private var headerView: some View {
HStack {
Text("Tasks")
.font(.headline)
.fontWeight(.bold)
Spacer()
// Badge with remaining task count
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 with App Intent as action
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack(spacing: 12) {
// Completion indicator
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundStyle(task.isCompleted ? .green : .secondary)
// Task title
Text(task.title)
.font(.subheadline)
.strikethrough(task.isCompleted)
.foregroundStyle(task.isCompleted ? .secondary : .primary)
.lineLimit(1)
Spacer()
// Priority indicator
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.
import SwiftUI
import AppIntents
// Specific intent for Toggle with explicit state
struct SetTaskCompletionIntent: AppIntent {
static var title: LocalizedStringResource = "Set task state"
@Parameter(title: "Task ID")
var taskID: String
// Target state: true = completed, false = not completed
@Parameter(title: "Completed")
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 }) {
// Set state explicitly (not 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()
// Interactive toggle with 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 natürlich in iOS-Designs.
Widgets können keine Alerts, Sheets oder Navigation anzeigen. Alle Aktionen müssen eigenständig 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.
import SwiftUI
import WidgetKit
struct AnimatedTaskRowView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack(spacing: 12) {
// Icon with transition animation
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundStyle(task.isCompleted ? .green : .secondary)
// Icon animation on change
.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)
)
// Background animation
.animation(.easeInOut(duration: 0.3), value: task.isCompleted)
}
.buttonStyle(.plain)
}
}
// Widget with animated invalidation
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("Animated Tasks")
.description("Widgets with smooth animations.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
// Enable content animations
.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("Tasks")
.font(.headline.bold())
Spacer()
let completed = entry.tasks.filter(\.isCompleted).count
let total = entry.tasks.count
// Animated progress
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.
iOS 26 Liquid Glass und Akzentuiertes Rendering
iOS 26 führte Liquid Glass Material und akzentuierte Rendering-Modi für Widgets ein. Diese visuellen Updates werden automatisch angewendet, wenn .containerBackground verwendet wird, aber Entwickler können das Rendering-Verhalten steuern.
import SwiftUI
import WidgetKit
struct ModernTaskWidgetView: View {
let entry: TaskEntry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(entry.tasks.prefix(3)) { task in
TaskRowView(task: task)
}
}
.padding()
// iOS 26: Control accented rendering for images
.widgetAccentedRenderingMode(.fullColor)
}
}
// Extra large portrait family (iOS 26+)
struct LargePortraitTaskWidget: Widget {
let kind: String = "LargePortraitTaskWidget"
var body: some WidgetConfiguration {
StaticConfiguration(
kind: kind,
provider: TaskTimelineProvider()
) { entry in
LargePortraitTaskWidgetView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Task Overview")
.description("Full task overview with extra space.")
// New in iOS 26: system extra large portrait
.supportedFamilies([.systemExtraLarge])
}
}
struct LargePortraitTaskWidgetView: View {
let entry: TaskEntry
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("All Tasks")
.font(.title2.bold())
// More space for task details
ForEach(entry.tasks) { task in
DetailedTaskRowView(task: task)
}
Spacer(minLength: 0)
}
.padding()
}
}
struct DetailedTaskRowView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskIntent(taskID: task.id)) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isCompleted ? .green : .secondary)
Text(task.title)
.font(.headline)
.strikethrough(task.isCompleted)
}
Text("Priority: \(task.priority.rawValue.capitalized)")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(.systemBackground).opacity(0.3))
.cornerRadius(12)
}
.buttonStyle(.plain)
}
}Der Modifier .widgetAccentedRenderingMode(.fullColor) bewahrt Bildfarben in akzentuierten Umgebungen. Die .systemExtraLarge Familie, ursprünglich von visionOS 26, ist jetzt auf iOS und iPadOS verfügbar.
WWDC 2026 App Intents: EntityCollection für Performance
WWDC 2026 führte EntityCollection ein, um große Mengen von Entitäten effizient zu handhaben. Anstatt alle Entitäten vor der Intent-Ausführung aufzulösen, werden nur Identifikatoren übergeben.
import AppIntents
import WidgetKit
// Mark multiple tasks complete at once - efficiently
struct BatchCompleteTasksIntent: AppIntent {
static var title: LocalizedStringResource = "Complete multiple tasks"
// EntityCollection passes IDs only, not resolved entities
@Parameter(title: "Tasks")
var tasks: EntityCollection<TaskEntity>
init() {}
init(taskIDs: [UUID]) {
// Create collection from IDs
self.tasks = EntityCollection(taskIDs.map { TaskEntity(id: $0) })
}
func perform() async throws -> some IntentResult {
// Access IDs directly without resolution overhead
let taskIDs = tasks.identifiers
var allTasks = TaskDataManager.shared.fetchTasks()
for id in taskIDs {
if let uuid = UUID(uuidString: id),
let index = allTasks.firstIndex(where: { $0.id == uuid }) {
allTasks[index].isCompleted = true
}
}
TaskDataManager.shared.saveTasks(allTasks)
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// Entity conformance for TaskEntity
struct TaskEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Task"
static var defaultQuery = TaskEntityQuery()
var id: UUID
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "Task \(id.uuidString.prefix(8))")
}
}
struct TaskEntityQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
identifiers.map { TaskEntity(id: $0) }
}
}Dieses Muster ist entscheidend für Massenoperationen. Apples interne Benchmarks zeigten, dass das Taggen von 1000 Fotos mit EntityCollection von langsam zu "nahezu sofort" wurde.
Lang laufende Intents mit Fortschrittsanzeige
WWDC 2026 führte auch LongRunningIntent für Operationen ein, die das standardmäßige 30-Sekunden-Limit überschreiten, mit Fortschrittsanzeige als Live Activity.
import AppIntents
import WidgetKit
struct SyncAllTasksIntent: LongRunningIntent, CancellableIntent {
static var title: LocalizedStringResource = "Sync all tasks"
func perform() async throws -> some IntentResult & ProvidesDialog {
let result = try await performBackgroundTask {
let tasks = TaskDataManager.shared.fetchTasks()
progress.totalUnitCount = Int64(tasks.count)
for (index, task) in tasks.enumerated() {
// Check for cancellation
try Task.checkCancellation()
// Simulate server sync
try await Task.sleep(for: .milliseconds(100))
progress.completedUnitCount = Int64(index + 1)
}
return "Synced \(tasks.count) tasks"
} onCancel: { reason in
// Cleanup based on cancellation reason
switch reason {
case .user:
print("User cancelled sync")
case .timeout:
print("System timeout")
case .systemReclaim:
print("System reclaimed resources")
@unknown default:
break
}
}
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result(dialog: "\(result)")
}
}Der Fortschritt erscheint als Live Activity, und CancellableIntent liefert den Grund für den Abbruch, damit die Bereinigung entsprechend erfolgen kann. Für mehr über Swift Concurrency Muster siehe Swift Structured Concurrency.
Konfigurierbares Widget mit AppIntentConfiguration
Für vom Benutzer anpassbare Widgets (Filter, Kategorien) ersetzt AppIntentConfiguration die StaticConfiguration.
import WidgetKit
import SwiftUI
import AppIntents
// Configuration exposed to user
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Task configuration"
static var description = IntentDescription("Customize task display.")
// Filter by priority
@Parameter(title: "Priority", default: .all)
var priorityFilter: PriorityFilter
// Show completed tasks
@Parameter(title: "Show completed", default: true)
var showCompleted: Bool
// Maximum number of tasks
@Parameter(title: "Task count", default: 3)
var maxTasks: Int
}
// Enum for priority filter
enum PriorityFilter: String, AppEnum {
case all
case high
case medium
case low
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priority"
static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
.all: "All",
.high: "High",
.medium: "Medium",
.low: "Low"
]
}
// Provider adapted to configuration
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))
}
// Apply configuration filters
private func filteredTasks(for config: TaskWidgetConfigurationIntent) -> [Task] {
var tasks = TaskDataManager.shared.fetchTasks()
// Filter by priority
if config.priorityFilter != .all {
let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
tasks = tasks.filter { $0.priority == priority }
}
// Filter completed if needed
if !config.showCompleted {
tasks = tasks.filter { !$0.isCompleted }
}
// Limit count
return Array(tasks.prefix(config.maxTasks))
}
}
// Widget with user configuration
struct ConfigurableTaskWidget: Widget {
let kind: String = "ConfigurableTaskWidget"
var body: some WidgetConfiguration {
// AppIntentConfiguration for configurable widgets
AppIntentConfiguration(
kind: kind,
intent: TaskWidgetConfigurationIntent.self,
provider: ConfigurableTaskProvider()
) { entry in
TaskWidgetView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Custom Tasks")
.description("Filter and customize your tasks.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}Der Benutzer kann das Widget durch langes Drücken konfigurieren und so ein personalisiertes Erlebnis ohne zusätzlichen Code in der Anwendung erhalten. Für mehr über App Intents und Siri-Integration siehe App Intents 2.0 und Siri Shortcuts.
Best Practices und Optimierungen
Mehrere Muster gewährleisten leistungsstarke und zuverlässige interaktive Widgets.
import WidgetKit
import SwiftUI
// 1. Always invalidate cache after modification
final class WidgetRefreshManager {
static func refreshAllWidgets() {
// Refresh all app widgets
WidgetCenter.shared.reloadAllTimelines()
}
static func refreshWidget(kind: String) {
// Refresh specific widget
WidgetCenter.shared.reloadTimelines(ofKind: kind)
}
// Call from app after data modification
static func notifyDataChanged() {
Task { @MainActor in
refreshAllWidgets()
}
}
}
// 2. Limit view complexity
struct OptimizedWidgetView: View {
let entry: TaskEntry
var body: some View {
// Prefer simple views without GeometryReader
VStack(alignment: .leading, spacing: 8) {
ForEach(entry.tasks.prefix(3)) { task in
// Lightweight components
minimalTaskRow(task)
}
}
.padding()
}
// Minimal view for 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. Use @AppStorage for simple state
struct QuickSettingsWidgetView: View {
// Direct access to shared UserDefaults
@AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
private var showCompleted = true
var body: some View {
// State persists between refreshes
Text(showCompleted ? "Showing all" : "Hiding completed")
}
}
// 4. Pre-calculate data in provider
struct OptimizedTaskEntry: TimelineEntry {
let date: Date
let tasks: [Task]
// Pre-calculated data
let completedCount: Int
let pendingCount: Int
let highPriorityCount: Int
init(date: Date, tasks: [Task]) {
self.date = date
self.tasks = tasks
// Calculations performed once
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.
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.
Quellen
- WidgetKit Dokumentation - Apples offizielle WidgetKit-Referenz
- App Intents Framework - Vollständige App Intents API-Dokumentation
- WWDC 2026: Discover new capabilities in the App Intents framework - EntityCollection, LongRunningIntent, SyncableEntity
- Human Interface Guidelines: Materials - Liquid Glass Design-Richtlinien
Checkliste für Interaktive iOS Widget-Entwicklung
WidgetKit mit App Intents verwandelt Widgets in echte interaktive Erweiterungen von iOS-Anwendungen. Die in iOS 17 eingeführte Architektur bleibt bis iOS 26 stabil, mit visuellen Verfeinerungen und Performance-Features, die im Laufe der Zeit hinzugefügt wurden.
Implementierungs-Checkliste
- App Group für Datenaustausch zwischen App und Widget-Extension konfigurieren
- Timeline Provider mit angemessener Aktualisierungsrichtlinie erstellen
- App Intents für jede Benutzeraktion implementieren
Button(intent:)oderToggle(intent:)für Interaktivität verwendenWidgetCenter.shared.reloadTimelinesnach Datenänderung aufrufen- Pflicht-
.containerBackgroundfür iOS 17+ Kompatibilität hinzufügen .widgetAccentedRenderingModefür iOS 26 Liquid Glass Unterstützung verwenden- Weiche Übergangsanimationen mit
.contentTransitionimplementieren - Lade- und Fehlerzustände für visuelles Feedback behandeln
EntityCollectionfür Massenoperationen verwenden, um Resolution-Overhead zu vermeiden- Auf allen unterstützten Widget-Größen inklusive
.systemExtraLargetesten
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Findest du den Bug in iOS?
Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Geschrieben von
Anthony Fillion-MailletGründer von SharpSkill
Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.
Aktualisiert am 24. August 2026
Tags
Teilen
Verwandte Artikel

App Intents 2.0 und Siri Shortcuts: iOS 27 Automatisierungs-Guide
Vollständiger Leitfaden zu App Intents 2.0 und Siri Shortcuts für iOS 27. Streaming-Antworten, Mehrschritt-Konversationen, View Annotations und Foundation Models Integration.

iOS-Entwickler Jobs 2026: Jobbörsen, Gehaltsübersicht und Interviewvorbereitung
Der Arbeitsmarkt für iOS-Entwickler 2026: Wo Stellenangebote zu finden sind, aktuelle Gehaltsspannen und wie sich Bewerber optimal auf technische Interviews vorbereiten.

Swift Package Manager 2026: Pakete erstellen, veröffentlichen und Interview-Fragen meistern
Umfassende Anleitung zum Swift Package Manager für iOS-Entwickler. Pakete erstellen, Abhängigkeiten verwalten, Bibliotheken veröffentlichen und häufige SPM-Interview-Fragen beantworten.