WidgetKit iOS 17+: Widget Interaktif dengan App Intents

Panduan lengkap untuk membuat widget iOS interaktif dengan WidgetKit dan App Intents. Tombol, toggle, animasi, dan praktik terbaik untuk iOS 17+ pada 2026.

WidgetKit iOS 17+ dengan widget interaktif dan App Intents untuk aplikasi iOS modern

iOS 17 telah merevolusi WidgetKit dengan memperkenalkan interaktivitas native. Widget tidak lagi merupakan tampilan statis: sekarang dapat merespons tindakan pengguna langsung dari layar utama, tanpa membuka aplikasi. Evolusi penting ini bertumpu pada framework App Intents dan menyediakan pengalaman pengguna yang lancar dan modern.

Yang dibahas artikel ini

Artikel ini menyajikan pembuatan lengkap widget interaktif iOS 17+, mulai dari konfigurasi proyek hingga pola lanjutan dengan animasi dan manajemen state.

Arsitektur Widget Interaktif

Interaktivitas widget iOS 17+ bekerja melalui framework App Intents. Berbeda dengan deep link tradisional yang akan membuka aplikasi, App Intents memungkinkan eksekusi kode langsung dari widget, kemudian secara otomatis memperbarui tampilan dengan data baru.

InteractiveWidgetArchitecture.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Arsitektur didasarkan pada tiga komponen utama:
// 1. Widget Timeline Provider - menyediakan data
// 2. Widget View - menampilkan antarmuka dengan Button/Toggle
// 3. App Intent - menjalankan tindakan saat disentuh

struct TaskWidget: Widget {
    // Identifier unik widget
    let kind: String = "TaskWidget"

    var body: some WidgetConfiguration {
        // StaticConfiguration untuk widget tanpa parameter
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                // Wajib untuk App Intents
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Tugas")
        .description("Kelola tugas Anda dari layar utama.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

Widget mendeklarasikan konfigurasinya dan menentukan provider yang akan menyediakan data. Atribut .containerBackground wajib sejak iOS 17 untuk widget interaktif.

Pembuatan Timeline Provider

Timeline Provider menentukan kapan dan bagaimana widget diperbarui. Untuk widget interaktif, ia juga harus merespons perubahan yang dipicu oleh App Intents.

TaskTimelineProvider.swiftswift
import WidgetKit
import SwiftUI

// Entry yang merepresentasikan state widget pada momen tertentu
struct TaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // State pemuatan untuk feedback visual
    var isLoading: Bool = false
}

// Model data yang dibagikan antara aplikasi dan 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 yang ditampilkan saat pemuatan awal
    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(
            date: Date(),
            tasks: [
                Task(id: UUID(), title: "Tugas contoh", isCompleted: false, priority: .medium)
            ]
        )
    }

    // Snapshot untuk galeri 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 lengkap dengan kebijakan refresh
    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 dalam 15 menit atau setelah tindakan pengguna
        let nextUpdate = Calendar.current.date(
            byAdding: .minute,
            value: 15,
            to: Date()
        ) ?? Date()

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

Provider menggunakan TaskDataManager bersama untuk mengakses data. Pendekatan ini menjamin sinkronisasi antara aplikasi utama dan widget.

App Group wajib

Untuk berbagi data antara aplikasi dan widget, perlu mengonfigurasi App Group di capabilities proyek. UserDefaults atau file harus menggunakan grup bersama ini.

Manajer Data Bersama

Berbagi data antara aplikasi dan widget memerlukan kontainer bersama yang dapat diakses melalui App Group.

TaskDataManager.swiftswift
import Foundation

final class TaskDataManager {
    // Singleton untuk akses global
    static let shared = TaskDataManager()

    // Identifier App Group dikonfigurasi di Xcode
    private let appGroupID = "group.com.example.taskapp"

    // UserDefaults yang dibagikan antara aplikasi dan widget
    private var sharedDefaults: UserDefaults? {
        UserDefaults(suiteName: appGroupID)
    }

    private let tasksKey = "tasks"

    private init() {}

    // Mengambil tugas dari penyimpanan bersama
    func fetchTasks() -> [Task] {
        guard let data = sharedDefaults?.data(forKey: tasksKey),
              let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
            return []
        }
        return tasks
    }

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

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

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

Manajer ini mengenkapsulasi semua logika persistensi dan akan digunakan baik oleh aplikasi maupun App Intents widget.

Pembuatan App Intent untuk Interaktivitas

App Intent mendefinisikan tindakan yang dijalankan ketika pengguna berinteraksi dengan widget. iOS menjalankan tindakan ini di latar belakang kemudian secara otomatis memperbarui widget.

ToggleTaskIntent.swiftswift
import AppIntents
import WidgetKit

// Intent untuk mengubah status tugas
struct ToggleTaskIntent: AppIntent {
    // Judul yang ditampilkan di pintasan Siri
    static var title: LocalizedStringResource = "Ubah status tugas"

    // Deskripsi untuk aksesibilitas
    static var description = IntentDescription("Menandai tugas sebagai selesai atau belum selesai.")

    // Parameter: ID tugas yang akan diubah
    @Parameter(title: "ID tugas")
    var taskID: String

    // Initializer wajib untuk AppIntent
    init() {}

    // Initializer dengan parameter untuk pembuatan dari view
    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // Eksekusi tindakan
    func perform() async throws -> some IntentResult {
        // Konversi string ID ke UUID
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        // Pembaruan tugas
        TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)

        // Meminta refresh widget
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

Panggilan ke WidgetCenter.shared.reloadTimelines memicu refresh segera widget setelah tindakan, memastikan feedback visual instan.

Siap menguasai wawancara iOS Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

View Widget dengan Tombol Interaktif

View widget menggunakan komponen Button standar SwiftUI dengan intent sebagai tindakan. iOS 17+ secara otomatis menangkap interaksi ini untuk menjalankan App Intent.

TaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct TaskWidgetView: View {
    let entry: TaskEntry

    // Adaptasi terhadap ukuran widget
    @Environment(\.widgetFamily) var family

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Header dengan judul dan penghitung
            headerView

            // Daftar tugas dengan tombol interaktif
            ForEach(entry.tasks.prefix(tasksLimit)) { task in
                TaskRowView(task: task)
            }

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

    // Jumlah tugas berdasarkan ukuran
    private var tasksLimit: Int {
        switch family {
        case .systemSmall: return 2
        case .systemMedium: return 3
        default: return 4
        }
    }

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

            Spacer()

            // Badge dengan jumlah tugas yang tersisa
            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 {
        // Tombol dengan App Intent sebagai tindakan
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Indikator selesai
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

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

                Spacer()

                // Indikator prioritas
                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()
        }
    }
}

Sintaks Button(intent:) secara langsung menghubungkan tombol ke App Intent. Saat disentuh, iOS menjalankan perform() kemudian secara otomatis memperbarui widget.

Toggle Interaktif untuk Widget

Untuk tindakan tipe on/off, komponen Toggle menawarkan alternatif tombol dengan gaya iOS native.

ToggleWidgetView.swiftswift
import SwiftUI
import AppIntents

// Intent khusus untuk Toggle dengan state eksplisit
struct SetTaskCompletionIntent: AppIntent {
    static var title: LocalizedStringResource = "Tetapkan status tugas"

    @Parameter(title: "ID tugas")
    var taskID: String

    // State target: true = selesai, false = belum selesai
    @Parameter(title: "Selesai")
    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 }) {
            // Menetapkan state secara eksplisit (bukan 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 interaktif dengan intent
            Toggle(
                isOn: task.isCompleted,
                intent: SetTaskCompletionIntent(
                    taskID: task.id,
                    isCompleted: !task.isCompleted
                )
            )
            .toggleStyle(.switch)
            .labelsHidden()
        }
        .padding(.vertical, 4)
    }
}

Toggle menyediakan interaksi yang lebih intuitif untuk state biner dan terintegrasi secara alami dalam desain iOS.

Batasan widget interaktif

Widget tidak dapat menampilkan alert, sheet, atau navigasi. Semua tindakan harus mandiri dan memperbarui state yang terlihat secara langsung.

Animasi Refresh dan Transisi

iOS 17+ memungkinkan animasi transisi selama refresh widget setelah tindakan. Modifier .contentTransition mengontrol animasi ini.

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) {
                // Ikon dengan animasi transisi
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)
                    // Animasi ikon saat berubah
                    .contentTransition(.symbolEffect(.replace))

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

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

// Widget dengan invalidasi animasi
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("Tugas Beranimasi")
        .description("Widget dengan animasi halus.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
        // Aktivasi animasi konten
        .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("Tugas")
                .font(.headline.bold())

            Spacer()

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

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

Animasi .symbolEffect(.replace) dan .numericText() menciptakan transisi halus antar state, secara signifikan meningkatkan pengalaman pengguna.

Widget yang Dapat Dikonfigurasi dengan AppIntentConfiguration

Untuk widget yang dapat disesuaikan oleh pengguna (filter, kategori), AppIntentConfiguration menggantikan StaticConfiguration.

ConfigurableTaskWidget.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Konfigurasi yang diekspos kepada pengguna
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Konfigurasi tugas"
    static var description = IntentDescription("Sesuaikan tampilan tugas.")

    // Filter berdasarkan prioritas
    @Parameter(title: "Prioritas", default: .all)
    var priorityFilter: PriorityFilter

    // Tampilkan tugas selesai
    @Parameter(title: "Tampilkan selesai", default: true)
    var showCompleted: Bool

    // Jumlah maksimum tugas
    @Parameter(title: "Jumlah tugas", default: 3)
    var maxTasks: Int
}

// Enum untuk filter prioritas
enum PriorityFilter: String, AppEnum {
    case all
    case high
    case medium
    case low

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioritas"

    static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
        .all: "Semua",
        .high: "Tinggi",
        .medium: "Sedang",
        .low: "Rendah"
    ]
}

// Provider yang disesuaikan dengan konfigurasi
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))
    }

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

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

        // Filter selesai jika perlu
        if !config.showCompleted {
            tasks = tasks.filter { !$0.isCompleted }
        }

        // Batasi jumlah
        return Array(tasks.prefix(config.maxTasks))
    }
}

// Widget dengan konfigurasi pengguna
struct ConfigurableTaskWidget: Widget {
    let kind: String = "ConfigurableTaskWidget"

    var body: some WidgetConfiguration {
        // AppIntentConfiguration untuk widget yang dapat dikonfigurasi
        AppIntentConfiguration(
            kind: kind,
            intent: TaskWidgetConfigurationIntent.self,
            provider: ConfigurableTaskProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Tugas Disesuaikan")
        .description("Filter dan sesuaikan tugas Anda.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

Pengguna sekarang dapat mengonfigurasi widget melalui tekan lama, menyediakan pengalaman yang dipersonalisasi tanpa kode tambahan dalam aplikasi.

Penanganan Error dan State Pemuatan

UX yang baik memerlukan penanganan kasus error dan state perantara selama interaksi.

TaskIntentWithFeedback.swiftswift
import AppIntents
import WidgetKit

struct ToggleTaskWithFeedbackIntent: AppIntent {
    static var title: LocalizedStringResource = "Ubah tugas dengan feedback"

    @Parameter(title: "ID tugas")
    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 {
            // Mengembalikan kegagalan diam
            return .result(value: false)
        }

        // Mensimulasikan operasi async (misalnya sinkronisasi server)
        do {
            try await Task.sleep(for: .milliseconds(100))

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

            return .result(value: true)
        } catch {
            // Error: jangan perbarui widget
            return .result(value: false)
        }
    }
}

// View dengan state pemuatan
struct TaskRowWithLoadingView: View {
    let task: Task
    @State private var isLoading = false

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

Feedback visual segera (opacity berkurang, indikator pemuatan) memberi tahu pengguna bahwa tindakan mereka telah didaftarkan.

Praktik Terbaik dan Optimalisasi

Beberapa pola memastikan widget interaktif yang berkinerja tinggi dan andal.

WidgetBestPractices.swiftswift
import WidgetKit
import SwiftUI

// 1. Selalu invalidasi cache setelah modifikasi
final class WidgetRefreshManager {
    static func refreshAllWidgets() {
        // Refresh semua widget aplikasi
        WidgetCenter.shared.reloadAllTimelines()
    }

    static func refreshWidget(kind: String) {
        // Refresh widget tertentu
        WidgetCenter.shared.reloadTimelines(ofKind: kind)
    }

    // Panggilan dari aplikasi setelah modifikasi data
    static func notifyDataChanged() {
        Task { @MainActor in
            refreshAllWidgets()
        }
    }
}

// 2. Batasi kompleksitas view
struct OptimizedWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        // Lebih sukai view sederhana tanpa GeometryReader
        VStack(alignment: .leading, spacing: 8) {
            ForEach(entry.tasks.prefix(3)) { task in
                // Komponen ringan
                minimalTaskRow(task)
            }
        }
        .padding()
    }

    // View minimal untuk performa
    @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. Gunakan @AppStorage untuk state sederhana
struct QuickSettingsWidgetView: View {
    // Akses langsung ke UserDefaults bersama
    @AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
    private var showCompleted = true

    var body: some View {
        // State bertahan antar refresh
        Text(showCompleted ? "Menampilkan semua" : "Menyembunyikan selesai")
    }
}

// 4. Pra-hitung data dalam provider
struct OptimizedTaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Data yang sudah dipra-hitung
    let completedCount: Int
    let pendingCount: Int
    let highPriorityCount: Int

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

        // Perhitungan dilakukan sekali
        self.completedCount = tasks.filter(\.isCompleted).count
        self.pendingCount = tasks.filter { !$0.isCompleted }.count
        self.highPriorityCount = tasks.filter { $0.priority == .high && !$0.isCompleted }.count
    }
}

Optimalisasi ini memastikan widget responsif yang tidak menghabiskan baterai secara berlebihan.

Debugging widget

Gunakan skema widget di Xcode untuk debugging. Pratinjau canvas memungkinkan pengujian berbagai ukuran dan state tanpa instalasi pada perangkat.

Kesimpulan

WidgetKit iOS 17+ dengan App Intents mengubah widget menjadi ekstensi interaktif sejati dari aplikasi iOS. Arsitektur deklaratif ini secara signifikan menyederhanakan pengembangan sambil menyediakan pengalaman pengguna native dan lancar.

Daftar Periksa Widget Interaktif iOS 17+

  • ✅ Konfigurasikan App Group untuk berbagi data
  • ✅ Buat Timeline Provider dengan refresh yang sesuai
  • ✅ Implementasikan App Intents untuk setiap tindakan
  • ✅ Gunakan Button(intent:) atau Toggle(intent:) untuk interaktivitas
  • ✅ Panggil WidgetCenter.shared.reloadTimelines setelah modifikasi
  • ✅ Tambahkan .containerBackground wajib untuk iOS 17+
  • ✅ Implementasikan animasi transisi halus
  • ✅ Tangani state pemuatan dan error
  • ✅ Optimalkan view untuk performa baterai
  • ✅ Uji pada semua ukuran widget yang didukung

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tag

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

Bagikan

Artikel terkait