WidgetKit iOS 17+: Widget Interattivi con App Intents

Guida completa per creare widget iOS interattivi con WidgetKit e App Intents. Pulsanti, toggle, animazioni e best practice per iOS 17+ nel 2026.

WidgetKit iOS 17+ con widget interattivi e App Intents per applicazioni iOS moderne

iOS 17 ha rivoluzionato WidgetKit introducendo l'interattività nativa. I widget non sono più semplici visualizzazioni statiche: ora possono rispondere alle azioni dell'utente direttamente dalla schermata Home, senza aprire l'app. Questa importante evoluzione si basa sul framework App Intents e offre un'esperienza utente fluida e moderna.

Cosa copre questo articolo

Questo articolo presenta la creazione completa di widget interattivi iOS 17+, dalla configurazione del progetto ai pattern avanzati con animazioni e gestione dello stato.

Architettura dei Widget Interattivi

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.

InteractiveWidgetArchitecture.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// L'architettura si basa su tre componenti principali:
// 1. Widget Timeline Provider - fornisce i dati
// 2. Widget View - mostra l'interfaccia con Button/Toggle
// 3. App Intent - esegue l'azione al tocco

struct TaskWidget: Widget {
    // Identificatore univoco del widget
    let kind: String = "TaskWidget"

    var body: some WidgetConfiguration {
        // StaticConfiguration per widget senza parametri
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                // Obbligatorio per gli App Intents
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Attività")
        .description("Gestisci le tue attività dalla schermata Home.")
        .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.

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.

TaskTimelineProvider.swiftswift
import WidgetKit
import SwiftUI

// Entry che rappresenta lo stato del widget in un dato momento
struct TaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Stato di caricamento per il feedback visivo
    var isLoading: Bool = false
}

// Modello dati condiviso tra app e 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 mostrato durante il caricamento iniziale
    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(
            date: Date(),
            tasks: [
                Task(id: UUID(), title: "Attività di esempio", isCompleted: false, priority: .medium)
            ]
        )
    }

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

    // Timeline completa con policy di aggiornamento
    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)))

        // Aggiornamento tra 15 minuti o dopo un'azione utente
        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.

App Group obbligatorio

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.

TaskDataManager.swiftswift
import Foundation

final class TaskDataManager {
    // Singleton per accesso globale
    static let shared = TaskDataManager()

    // Identificatore App Group configurato in Xcode
    private let appGroupID = "group.com.example.taskapp"

    // UserDefaults condiviso tra app e widget
    private var sharedDefaults: UserDefaults? {
        UserDefaults(suiteName: appGroupID)
    }

    private let tasksKey = "tasks"

    private init() {}

    // Recupera le attività dalla memoria condivisa
    func fetchTasks() -> [Task] {
        guard let data = sharedDefaults?.data(forKey: tasksKey),
              let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
            return []
        }
        return tasks
    }

    // Salva con notifica al widget
    func saveTasks(_ tasks: [Task]) {
        guard let data = try? JSONEncoder().encode(tasks) else { return }
        sharedDefaults?.set(data, forKey: tasksKey)
    }

    // Aggiorna un'attività specifica
    func updateTask(_ task: Task) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == task.id }) {
            tasks[index] = task
            saveTasks(tasks)
        }
    }

    // Inverte lo stato di completamento
    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.

ToggleTaskIntent.swiftswift
import AppIntents
import WidgetKit

// Intent per invertire lo stato di un'attività
struct ToggleTaskIntent: AppIntent {
    // Titolo visualizzato nelle scorciatoie Siri
    static var title: LocalizedStringResource = "Inverti stato attività"

    // Descrizione per accessibilità
    static var description = IntentDescription("Contrassegna un'attività come completata o non completata.")

    // Parametro: ID dell'attività da modificare
    @Parameter(title: "ID attività")
    var taskID: String

    // Inizializzatore richiesto da AppIntent
    init() {}

    // Inizializzatore con parametro per la creazione dalla view
    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // Esecuzione dell'azione
    func perform() async throws -> some IntentResult {
        // Conversione dell'ID stringa in UUID
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        // Aggiornamento dell'attività
        TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)

        // Richiede l'aggiornamento del widget
        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.

TaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct TaskWidgetView: View {
    let entry: TaskEntry

    // Adattamento alla dimensione del widget
    @Environment(\.widgetFamily) var family

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Header con titolo e contatore
            headerView

            // Lista attività con pulsanti interattivi
            ForEach(entry.tasks.prefix(tasksLimit)) { task in
                TaskRowView(task: task)
            }

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

    // Numero di attività in base alla dimensione
    private var tasksLimit: Int {
        switch family {
        case .systemSmall: return 2
        case .systemMedium: return 3
        default: return 4
        }
    }

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

            Spacer()

            // Badge con il numero di attività rimanenti
            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 {
        // Pulsante con App Intent come azione
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Indicatore di completamento
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

                // Titolo dell'attività
                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)
                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
                    .lineLimit(1)

                Spacer()

                // Indicatore di priorità
                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.

ToggleWidgetView.swiftswift
import SwiftUI
import AppIntents

// Intent specifico per Toggle con stato esplicito
struct SetTaskCompletionIntent: AppIntent {
    static var title: LocalizedStringResource = "Imposta stato attività"

    @Parameter(title: "ID attività")
    var taskID: String

    // Stato desiderato: true = completata, false = non completata
    @Parameter(title: "Completata")
    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 }) {
            // Imposta lo stato in modo esplicito (non 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()

            // Toggle interattivo con 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.

Limitazioni dei widget interattivi

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.

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) {
                // Icona con animazione di transizione
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)
                    // Animazione dell'icona al cambiamento
                    .contentTransition(.symbolEffect(.replace))

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

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

// Widget con invalidazione animata
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("Attività Animate")
        .description("Widget con animazioni fluide.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
        // Attivazione delle animazioni di contenuto
        .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("Attività")
                .font(.headline.bold())

            Spacer()

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

            // Progresso animato
            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.

Widget Configurabile con AppIntentConfiguration

Per i widget personalizzabili dall'utente (filtri, categorie), AppIntentConfiguration sostituisce StaticConfiguration.

ConfigurableTaskWidget.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Configurazione esposta all'utente
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Configurazione attività"
    static var description = IntentDescription("Personalizza la visualizzazione delle attività.")

    // Filtro per priorità
    @Parameter(title: "Priorità", default: .all)
    var priorityFilter: PriorityFilter

    // Mostra attività completate
    @Parameter(title: "Mostra completate", default: true)
    var showCompleted: Bool

    // Numero massimo di attività
    @Parameter(title: "Numero attività", default: 3)
    var maxTasks: Int
}

// Enum per il filtro priorità
enum PriorityFilter: String, AppEnum {
    case all
    case high
    case medium
    case low

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priorità"

    static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
        .all: "Tutte",
        .high: "Alta",
        .medium: "Media",
        .low: "Bassa"
    ]
}

// Provider adattato alla configurazione
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))
    }

    // Applica i filtri di configurazione
    private func filteredTasks(for config: TaskWidgetConfigurationIntent) -> [Task] {
        var tasks = TaskDataManager.shared.fetchTasks()

        // Filtro per priorità
        if config.priorityFilter != .all {
            let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
            tasks = tasks.filter { $0.priority == priority }
        }

        // Filtra le completate se necessario
        if !config.showCompleted {
            tasks = tasks.filter { !$0.isCompleted }
        }

        // Limita il numero
        return Array(tasks.prefix(config.maxTasks))
    }
}

// Widget con configurazione utente
struct ConfigurableTaskWidget: Widget {
    let kind: String = "ConfigurableTaskWidget"

    var body: some WidgetConfiguration {
        // AppIntentConfiguration per widget configurabili
        AppIntentConfiguration(
            kind: kind,
            intent: TaskWidgetConfigurationIntent.self,
            provider: ConfigurableTaskProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Attività Personalizzate")
        .description("Filtra e personalizza le tue attività.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

L'utente può ora configurare il widget tramite pressione prolungata, offrendo un'esperienza personalizzata senza codice aggiuntivo nell'applicazione.

Gestione degli Errori e degli Stati di Caricamento

Una buona UX richiede di gestire i casi di errore e gli stati intermedi durante le interazioni.

TaskIntentWithFeedback.swiftswift
import AppIntents
import WidgetKit

struct ToggleTaskWithFeedbackIntent: AppIntent {
    static var title: LocalizedStringResource = "Inverti attività con feedback"

    @Parameter(title: "ID attività")
    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 {
            // Restituisce errore silenzioso
            return .result(value: false)
        }

        // Simula un'operazione asincrona (sync server per esempio)
        do {
            try await Task.sleep(for: .milliseconds(100))

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

            return .result(value: true)
        } catch {
            // Errore: non aggiornare il widget
            return .result(value: false)
        }
    }
}

// View con stato di caricamento
struct TaskRowWithLoadingView: View {
    let task: Task
    @State private var isLoading = false

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

Un feedback visivo immediato (opacità ridotta, indicatore di caricamento) informa l'utente che la sua azione è stata registrata.

Best Practice e Ottimizzazioni

Diversi pattern garantiscono widget interattivi performanti e affidabili.

WidgetBestPractices.swiftswift
import WidgetKit
import SwiftUI

// 1. Invalidare sempre la cache dopo una modifica
final class WidgetRefreshManager {
    static func refreshAllWidgets() {
        // Aggiornamento di tutti i widget dell'app
        WidgetCenter.shared.reloadAllTimelines()
    }

    static func refreshWidget(kind: String) {
        // Aggiornamento di un widget specifico
        WidgetCenter.shared.reloadTimelines(ofKind: kind)
    }

    // Chiamata dall'app dopo modifica dei dati
    static func notifyDataChanged() {
        Task { @MainActor in
            refreshAllWidgets()
        }
    }
}

// 2. Limitare la complessità delle view
struct OptimizedWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        // Preferire view semplici senza GeometryReader
        VStack(alignment: .leading, spacing: 8) {
            ForEach(entry.tasks.prefix(3)) { task in
                // Componenti leggeri
                minimalTaskRow(task)
            }
        }
        .padding()
    }

    // View minimale per le 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. Usare @AppStorage per stati semplici
struct QuickSettingsWidgetView: View {
    // Accesso diretto agli UserDefaults condivisi
    @AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
    private var showCompleted = true

    var body: some View {
        // Lo stato persiste tra gli aggiornamenti
        Text(showCompleted ? "Mostro tutte" : "Nascondo completate")
    }
}

// 4. Pre-calcolare i dati nel provider
struct OptimizedTaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Dati pre-calcolati
    let completedCount: Int
    let pendingCount: Int
    let highPriorityCount: Int

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

        // Calcoli effettuati una sola volta
        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.

Debug dei widget

Utilizzare lo schema widget in Xcode per il debug. La preview canvas permette di testare diverse dimensioni e stati senza installazione su dispositivo.

Conclusione

WidgetKit iOS 17+ con App Intents trasforma i widget in vere estensioni interattive delle applicazioni iOS. Questa architettura dichiarativa semplifica notevolmente lo sviluppo offrendo un'esperienza utente nativa e fluida.

Checklist Widget Interattivo iOS 17+

  • ✅ Configurare un App Group per la condivisione dei dati
  • ✅ Creare un Timeline Provider con aggiornamento appropriato
  • ✅ Implementare App Intents per ogni azione
  • ✅ Usare Button(intent:) o Toggle(intent:) per l'interattività
  • ✅ Chiamare WidgetCenter.shared.reloadTimelines dopo ogni modifica
  • ✅ Aggiungere il .containerBackground obbligatorio per iOS 17+
  • ✅ Implementare animazioni di transizione fluide
  • ✅ Gestire gli stati di caricamento ed errore
  • ✅ Ottimizzare le view per le performance della batteria
  • ✅ Testare su tutte le dimensioni di widget supportate

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Tag

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

Condividi

Articoli correlati