WidgetKit iOS 17-26: Widget Interattivi con App Intents
Guida completa per creare widget iOS interattivi con WidgetKit e App Intents. Pulsanti, toggle, animazioni, iOS 26 Liquid Glass material e aggiornamenti WWDC 2026.

iOS 17 ha introdotto l'interattività nativa in WidgetKit, permettendo ai widget di rispondere alle azioni dell'utente direttamente dalla schermata Home senza aprire l'app. iOS 26 ha perfezionato questo con Liquid Glass material e modalità di rendering accentuate. Questa guida copre lo sviluppo di widget interattivi da iOS 17 a iOS 26.
Creazione di widget interattivi iOS 17-26, dalla configurazione del progetto ai pattern avanzati con animazioni, gestione dello stato e le ultime funzionalità App Intents di WWDC 2026.
Architettura dei Widget Interattivi in iOS 17+
L'interattività dei widget iOS 17+ funziona tramite il framework App Intents. A differenza dei deep link tradizionali che aprirebbero l'app, gli App Intents permettono di eseguire codice direttamente dal widget, aggiornando poi automaticamente la visualizzazione con i nuovi dati.
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])
}
}Il widget dichiara la sua configurazione e specifica il provider che fornirà i dati. L'attributo .containerBackground è obbligatorio da iOS 17 per i widget interattivi, e iOS 26 applica automaticamente lo stile Liquid Glass quando appropriato.
Creazione del Timeline Provider
Il Timeline Provider determina quando e come il widget si aggiorna. Per i widget interattivi, deve anche reagire ai cambiamenti causati dagli App Intents.
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)
}
}Il provider utilizza un TaskDataManager condiviso per accedere ai dati. Questo approccio garantisce la sincronizzazione tra l'applicazione principale e il widget. Per pattern correlati di gestione dello stato, vedere SwiftUI @Observable vs @State.
Per condividere dati tra app e widget, è necessario configurare un App Group nelle capabilities del progetto. UserDefaults o file devono utilizzare questo gruppo condiviso.
Il Gestore di Dati Condivisi
La condivisione dei dati tra applicazione e widget richiede un container comune accessibile tramite App Group.
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)
}
}
}Questo gestore incapsula tutta la logica di persistenza e sarà utilizzato sia dall'applicazione che dagli App Intents del widget.
Creazione dell'App Intent per l'Interattività
L'App Intent definisce l'azione eseguita quando l'utente interagisce con il widget. iOS esegue questa azione in background e poi aggiorna automaticamente il widget.
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()
}
}La chiamata a WidgetCenter.shared.reloadTimelines provoca un aggiornamento immediato del widget dopo l'azione, garantendo un feedback visivo istantaneo.
Pronto a superare i tuoi colloqui su iOS?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
View del Widget con Pulsanti Interattivi
La view del widget utilizza il componente Button standard di SwiftUI con l'intent come azione. iOS 17+ intercetta automaticamente queste interazioni per eseguire l'App Intent.
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()
}
}
}La sintassi Button(intent:) collega direttamente il pulsante all'App Intent. Al tocco, iOS esegue perform() e poi aggiorna automaticamente il widget.
Toggle Interattivo per i Widget
Per le azioni di tipo on/off, il componente Toggle offre un'alternativa al pulsante con uno stile nativo iOS.
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)
}
}Il Toggle offre un'interazione più intuitiva per gli stati binari e si integra naturalmente nei design iOS.
I widget non possono visualizzare alert, sheet o navigazione. Tutte le azioni devono essere autonome e aggiornare direttamente lo stato visibile.
Animazioni di Aggiornamento e Transizioni
iOS 17+ permette di animare le transizioni durante l'aggiornamento del widget dopo un'azione. Il modificatore .contentTransition controlla queste animazioni.
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())
}
}
}Le animazioni .symbolEffect(.replace) e .numericText() creano transizioni fluide tra gli stati, migliorando significativamente l'esperienza utente.
iOS 26 Liquid Glass e Rendering Accentuato
iOS 26 ha introdotto Liquid Glass material e modalità di rendering accentuate per i widget. Questi aggiornamenti visivi vengono applicati automaticamente quando si usa .containerBackground, ma gli sviluppatori possono controllare il comportamento del rendering.
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)
}
}Il modificatore .widgetAccentedRenderingMode(.fullColor) preserva i colori delle immagini in ambienti accentuati. La famiglia .systemExtraLarge, originariamente da visionOS 26, è ora disponibile su iOS e iPadOS.
WWDC 2026 App Intents: EntityCollection per le Performance
WWDC 2026 ha introdotto EntityCollection per gestire grandi numeri di entità in modo efficiente. Invece di risolvere tutte le entità prima dell'esecuzione dell'intent, vengono passati solo gli identificatori.
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) }
}
}Questo pattern è fondamentale per le operazioni in blocco. I benchmark interni di Apple hanno mostrato che il tagging di 1000 foto è passato da lento a "quasi istantaneo" con EntityCollection.
Intent a Lunga Esecuzione con Visualizzazione del Progresso
WWDC 2026 ha anche introdotto LongRunningIntent per operazioni che superano il limite standard di 30 secondi, con progresso visualizzato come 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)")
}
}Il progresso appare come Live Activity, e CancellableIntent fornisce il motivo della cancellazione per gestire la pulizia appropriatamente. Per maggiori informazioni sui pattern di concorrenza Swift, vedere Swift Structured Concurrency.
Widget Configurabile con AppIntentConfiguration
Per i widget personalizzabili dall'utente (filtri, categorie), AppIntentConfiguration sostituisce 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])
}
}L'utente può configurare il widget tramite pressione prolungata, offrendo un'esperienza personalizzata senza codice aggiuntivo nell'applicazione. Per maggiori informazioni su App Intents e integrazione Siri, vedere App Intents 2.0 e Siri Shortcuts.
Best Practice e Ottimizzazioni
Diversi pattern garantiscono widget interattivi performanti e affidabili.
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
}
}Queste ottimizzazioni garantiscono widget reattivi che non consumano eccessivamente la batteria.
Utilizzare lo schema widget in Xcode per il debug. La preview canvas permette di testare diverse dimensioni e stati senza installazione su dispositivo.
Fonti
- Documentazione WidgetKit - Riferimento ufficiale Apple per WidgetKit
- Framework App Intents - Documentazione completa API App Intents
- WWDC 2026: Discover new capabilities in the App Intents framework - EntityCollection, LongRunningIntent, SyncableEntity
- Human Interface Guidelines: Materials - Linee guida design Liquid Glass
Checklist per lo Sviluppo di Widget iOS Interattivi
WidgetKit con App Intents trasforma i widget in vere estensioni interattive delle applicazioni iOS. L'architettura introdotta in iOS 17 rimane stabile attraverso iOS 26, con raffinamenti visivi e funzionalità performance aggiunte nel tempo.
Checklist di implementazione
- Configurare App Group per la condivisione dei dati tra app e widget extension
- Creare Timeline Provider con policy di aggiornamento appropriata
- Implementare App Intents per ogni azione utente
- Usare
Button(intent:)oToggle(intent:)per l'interattività - Chiamare
WidgetCenter.shared.reloadTimelinesdopo la modifica dei dati - Aggiungere il
.containerBackgroundobbligatorio per compatibilità iOS 17+ - Usare
.widgetAccentedRenderingModeper supporto Liquid Glass iOS 26 - Implementare animazioni di transizione fluide con
.contentTransition - Gestire stati di caricamento ed errore per feedback visivo
- Usare
EntityCollectionper operazioni in blocco per evitare overhead di risoluzione - Testare su tutte le dimensioni widget supportate incluso
.systemExtraLarge
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Sapresti trovare il bug in iOS?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 24 agosto 2026
Tag
Condividi
Articoli correlati

App Intents 2.0 e Siri Shortcuts: guida all'automazione iOS 27
Guida completa ad App Intents 2.0 e Siri Shortcuts per iOS 27. Risposte streaming, conversazioni multi-turno, View Annotations e integrazione Foundation Models.

Lavoro sviluppatore iOS 2026: dove cercare, stipendi e preparazione ai colloqui
Il mercato del lavoro per sviluppatori iOS nel 2026: dove trovare offerte, fasce salariali attuali e come prepararsi efficacemente ai colloqui tecnici.

Swift Package Manager nel 2026: Creare, Pubblicare Pacchetti e Domande da Colloquio
Guida completa a Swift Package Manager per sviluppatori iOS. Come creare pacchetti, gestire dipendenze, pubblicare librerie e rispondere alle domande tecniche sui colloqui SPM.