WidgetKit iOS 17+: App Intents ile Etkileşimli Widget'lar

WidgetKit ve App Intents ile etkileşimli iOS widget'ları oluşturmak için kapsamlı kılavuz. iOS 17+ için 2026'da düğmeler, anahtarlar, animasyonlar ve en iyi uygulamalar.

Modern iOS uygulamaları için etkileşimli widget'lar ve App Intents ile WidgetKit iOS 17+

iOS 17, native etkileşim sunarak WidgetKit'te devrim yarattı. Widget'lar artık statik gösterimler değil: artık uygulamayı açmadan ana ekrandan doğrudan kullanıcı eylemlerine yanıt verebiliyorlar. Bu önemli evrim, App Intents framework'üne dayanıyor ve akıcı, modern bir kullanıcı deneyimi sunuyor.

Bu makalenin kapsamı

Bu makale, proje yapılandırmasından animasyonlar ve durum yönetimi ile gelişmiş kalıplara kadar iOS 17+ etkileşimli widget'ların eksiksiz oluşturulmasını sunmaktadır.

Etkileşimli Widget Mimarisi

iOS 17+ widget'larının etkileşimi App Intents framework'ü aracılığıyla çalışır. Uygulamayı açacak geleneksel deep link'lerin aksine, App Intents kodun doğrudan widget'tan yürütülmesine izin verir, ardından görüntüyü otomatik olarak yeni verilerle yeniler.

InteractiveWidgetArchitecture.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Mimari üç ana bileşene dayanır:
// 1. Widget Timeline Provider - verileri sağlar
// 2. Widget View - Button/Toggle ile arayüzü gösterir
// 3. App Intent - dokunduğunda eylemi yürütür

struct TaskWidget: Widget {
    // Benzersiz widget tanımlayıcısı
    let kind: String = "TaskWidget"

    var body: some WidgetConfiguration {
        // Parametresiz widget'lar için StaticConfiguration
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                // App Intents için zorunlu
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Görevler")
        .description("Ana ekrandan görevlerinizi yönetin.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

Widget yapılandırmasını bildirir ve verileri sağlayacak provider'ı belirtir. .containerBackground özelliği, etkileşimli widget'lar için iOS 17'den beri zorunludur.

Timeline Provider Oluşturma

Timeline Provider, widget'ın ne zaman ve nasıl yenileneceğini belirler. Etkileşimli widget'lar için App Intents tarafından tetiklenen değişikliklere de tepki vermelidir.

TaskTimelineProvider.swiftswift
import WidgetKit
import SwiftUI

// Belirli bir andaki widget durumunu temsil eden Entry
struct TaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Görsel geri bildirim için yükleme durumu
    var isLoading: Bool = false
}

// Uygulama ve widget arasında paylaşılan veri modeli
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 {
    // İlk yükleme sırasında gösterilen placeholder
    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(
            date: Date(),
            tasks: [
                Task(id: UUID(), title: "Örnek görev", isCompleted: false, priority: .medium)
            ]
        )
    }

    // Widget galerisi için snapshot
    func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
        let entry = TaskEntry(
            date: Date(),
            tasks: TaskDataManager.shared.fetchTasks().prefix(3).map { $0 }
        )
        completion(entry)
    }

    // Yenileme politikası ile tam timeline
    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)))

        // 15 dakika sonra veya kullanıcı eyleminden sonra yenileme
        let nextUpdate = Calendar.current.date(
            byAdding: .minute,
            value: 15,
            to: Date()
        ) ?? Date()

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

Provider, verilere erişmek için paylaşılan bir TaskDataManager kullanır. Bu yaklaşım, ana uygulama ve widget arasında senkronizasyonu garanti eder.

App Group zorunlu

Uygulama ve widget arasında veri paylaşmak için, proje capabilities'inde bir App Group yapılandırmak gerekir. UserDefaults veya dosyalar bu paylaşılan grubu kullanmalıdır.

Paylaşılan Veri Yöneticisi

Uygulama ve widget arasında veri paylaşımı, App Group aracılığıyla erişilebilen ortak bir konteyner gerektirir.

TaskDataManager.swiftswift
import Foundation

final class TaskDataManager {
    // Global erişim için Singleton
    static let shared = TaskDataManager()

    // Xcode'da yapılandırılmış App Group tanımlayıcısı
    private let appGroupID = "group.com.example.taskapp"

    // Uygulama ve widget arasında paylaşılan UserDefaults
    private var sharedDefaults: UserDefaults? {
        UserDefaults(suiteName: appGroupID)
    }

    private let tasksKey = "tasks"

    private init() {}

    // Görevleri paylaşılan depolamadan alır
    func fetchTasks() -> [Task] {
        guard let data = sharedDefaults?.data(forKey: tasksKey),
              let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
            return []
        }
        return tasks
    }

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

    // Belirli bir görevi günceller
    func updateTask(_ task: Task) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == task.id }) {
            tasks[index] = task
            saveTasks(tasks)
        }
    }

    // Tamamlanma durumunu değiştirir
    func toggleTaskCompletion(taskID: UUID) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == taskID }) {
            tasks[index].isCompleted.toggle()
            saveTasks(tasks)
        }
    }
}

Bu yönetici tüm kalıcılık mantığını kapsar ve hem uygulama hem de widget App Intents tarafından kullanılacaktır.

Etkileşim için App Intent Oluşturma

App Intent, kullanıcı widget ile etkileşime girdiğinde yürütülen eylemi tanımlar. iOS bu eylemi arka planda yürütür ve ardından widget'ı otomatik olarak yeniler.

ToggleTaskIntent.swiftswift
import AppIntents
import WidgetKit

// Görev durumunu değiştirme intent'i
struct ToggleTaskIntent: AppIntent {
    // Siri kısayollarında gösterilen başlık
    static var title: LocalizedStringResource = "Görev durumunu değiştir"

    // Erişilebilirlik için açıklama
    static var description = IntentDescription("Bir görevi tamamlandı veya tamamlanmadı olarak işaretler.")

    // Parametre: değiştirilecek görevin ID'si
    @Parameter(title: "Görev ID'si")
    var taskID: String

    // AppIntent için gerekli initializer
    init() {}

    // View'dan oluşturma için parametreli initializer
    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // Eylem yürütme
    func perform() async throws -> some IntentResult {
        // String ID'nin UUID'ye dönüşümü
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        // Görevin güncellenmesi
        TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)

        // Widget yenilemesi ister
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

WidgetCenter.shared.reloadTimelines çağrısı eylemden sonra widget'ı anında yeniler ve anlık görsel geri bildirim sağlar.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Etkileşimli Düğmelerle Widget Görünümü

Widget görünümü, eylem olarak intent ile standart SwiftUI Button bileşenini kullanır. iOS 17+, App Intent'i yürütmek için bu etkileşimleri otomatik olarak yakalar.

TaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct TaskWidgetView: View {
    let entry: TaskEntry

    // Widget boyutuna uyum
    @Environment(\.widgetFamily) var family

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Başlık ve sayaç ile header
            headerView

            // Etkileşimli düğmelerle görev listesi
            ForEach(entry.tasks.prefix(tasksLimit)) { task in
                TaskRowView(task: task)
            }

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

    // Boyuta göre görev sayısı
    private var tasksLimit: Int {
        switch family {
        case .systemSmall: return 2
        case .systemMedium: return 3
        default: return 4
        }
    }

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

            Spacer()

            // Kalan görev sayısı ile rozet
            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 {
        // Eylem olarak App Intent ile düğme
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Tamamlanma göstergesi
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

                // Görev başlığı
                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)
                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
                    .lineLimit(1)

                Spacer()

                // Öncelik göstergesi
                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()
        }
    }
}

Button(intent:) sözdizimi düğmeyi doğrudan App Intent'e bağlar. Dokunulduğunda iOS perform() yürütür ve ardından widget'ı otomatik olarak yeniler.

Widget'lar için Etkileşimli Toggle

Açık/kapalı türü eylemler için, Toggle bileşeni native iOS tarzıyla düğmeye alternatif sunar.

ToggleWidgetView.swiftswift
import SwiftUI
import AppIntents

// Açık durumlu Toggle için özel intent
struct SetTaskCompletionIntent: AppIntent {
    static var title: LocalizedStringResource = "Görev durumunu ayarla"

    @Parameter(title: "Görev ID'si")
    var taskID: String

    // Hedef durum: true = tamamlandı, false = tamamlanmadı
    @Parameter(title: "Tamamlandı")
    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 }) {
            // Durumu açıkça ayarlar (toggle değil)
            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()

            // Intent ile etkileşimli toggle
            Toggle(
                isOn: task.isCompleted,
                intent: SetTaskCompletionIntent(
                    taskID: task.id,
                    isCompleted: !task.isCompleted
                )
            )
            .toggleStyle(.switch)
            .labelsHidden()
        }
        .padding(.vertical, 4)
    }
}

Toggle, ikili durumlar için daha sezgisel bir etkileşim sağlar ve iOS tasarımlarına doğal olarak entegre olur.

Etkileşimli widget sınırlamaları

Widget'lar uyarılar, sheet veya gezinti gösteremez. Tüm eylemler bağımsız olmalı ve görünür durumu doğrudan güncellemelidir.

Yenileme Animasyonları ve Geçişler

iOS 17+, eylemden sonra widget yenileme sırasında geçişlerin animasyonuna izin verir. .contentTransition modifier'ı bu animasyonları kontrol eder.

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) {
                // Geçiş animasyonlu ikon
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)
                    // Değişimde ikon animasyonu
                    .contentTransition(.symbolEffect(.replace))

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

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

// Animasyonlu invalidasyon ile widget
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("Animasyonlu Görevler")
        .description("Akıcı animasyonlu widget'lar.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
        // İçerik animasyonlarının etkinleştirilmesi
        .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("Görevler")
                .font(.headline.bold())

            Spacer()

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

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

.symbolEffect(.replace) ve .numericText() animasyonları durumlar arasında akıcı geçişler oluşturur ve kullanıcı deneyimini önemli ölçüde iyileştirir.

AppIntentConfiguration ile Yapılandırılabilir Widget

Kullanıcı tarafından özelleştirilebilir widget'lar için (filtreler, kategoriler), AppIntentConfiguration StaticConfiguration'ın yerini alır.

ConfigurableTaskWidget.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// Kullanıcıya açık yapılandırma
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Görev yapılandırması"
    static var description = IntentDescription("Görev görüntüsünü özelleştirin.")

    // Önceliğe göre filtre
    @Parameter(title: "Öncelik", default: .all)
    var priorityFilter: PriorityFilter

    // Tamamlanan görevleri göster
    @Parameter(title: "Tamamlananları göster", default: true)
    var showCompleted: Bool

    // Maksimum görev sayısı
    @Parameter(title: "Görev sayısı", default: 3)
    var maxTasks: Int
}

// Öncelik filtresi için enum
enum PriorityFilter: String, AppEnum {
    case all
    case high
    case medium
    case low

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Öncelik"

    static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
        .all: "Tümü",
        .high: "Yüksek",
        .medium: "Orta",
        .low: "Düşük"
    ]
}

// Yapılandırmaya uyarlanmış provider
struct ConfigurableTaskProvider: AppIntentTimelineProvider {
    typealias Entry = TaskEntry
    typealias Intent = TaskWidgetConfigurationIntent

    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(date: Date(), tasks: [])
    }

    func snapshot(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> TaskEntry {
        let tasks = filteredTasks(for: configuration)
        return TaskEntry(date: Date(), tasks: tasks)
    }

    func timeline(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> Timeline<TaskEntry> {
        let tasks = filteredTasks(for: configuration)
        let entry = TaskEntry(date: Date(), tasks: tasks)

        let nextUpdate = Date().addingTimeInterval(15 * 60)
        return Timeline(entries: [entry], policy: .after(nextUpdate))
    }

    // Yapılandırma filtrelerini uygular
    private func filteredTasks(for config: TaskWidgetConfigurationIntent) -> [Task] {
        var tasks = TaskDataManager.shared.fetchTasks()

        // Önceliğe göre filtreleme
        if config.priorityFilter != .all {
            let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
            tasks = tasks.filter { $0.priority == priority }
        }

        // Gerekirse tamamlananları filtreleme
        if !config.showCompleted {
            tasks = tasks.filter { !$0.isCompleted }
        }

        // Sayı sınırlaması
        return Array(tasks.prefix(config.maxTasks))
    }
}

// Kullanıcı yapılandırmalı widget
struct ConfigurableTaskWidget: Widget {
    let kind: String = "ConfigurableTaskWidget"

    var body: some WidgetConfiguration {
        // Yapılandırılabilir widget'lar için AppIntentConfiguration
        AppIntentConfiguration(
            kind: kind,
            intent: TaskWidgetConfigurationIntent.self,
            provider: ConfigurableTaskProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("Özelleştirilmiş Görevler")
        .description("Görevlerinizi filtreleyin ve özelleştirin.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

Kullanıcı artık uzun basma ile widget'ı yapılandırabilir ve uygulamada ek kod olmadan kişiselleştirilmiş bir deneyim elde edebilir.

Hata Yönetimi ve Yükleme Durumları

İyi bir UX, etkileşimler sırasında hata durumlarını ve ara durumları yönetmeyi gerektirir.

TaskIntentWithFeedback.swiftswift
import AppIntents
import WidgetKit

struct ToggleTaskWithFeedbackIntent: AppIntent {
    static var title: LocalizedStringResource = "Görevi feedback ile değiştir"

    @Parameter(title: "Görev ID'si")
    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 {
            // Sessiz hata döndürür
            return .result(value: false)
        }

        // Async işlem simülasyonu (örn. sunucu sync)
        do {
            try await Task.sleep(for: .milliseconds(100))

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

            return .result(value: true)
        } catch {
            // Hata: widget'ı güncelleme
            return .result(value: false)
        }
    }
}

// Yükleme durumlu görünüm
struct TaskRowWithLoadingView: View {
    let task: Task
    @State private var isLoading = false

    var body: some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // Koşullu gösterge
                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)
    }
}

Anında görsel geri bildirim (azaltılmış opaklık, yükleme göstergesi) kullanıcıya eyleminin kaydedildiğini bildirir.

En İyi Uygulamalar ve Optimizasyonlar

Birkaç kalıp performanslı ve güvenilir etkileşimli widget'lar sağlar.

WidgetBestPractices.swiftswift
import WidgetKit
import SwiftUI

// 1. Modifikasyondan sonra her zaman önbelleği geçersiz kıl
final class WidgetRefreshManager {
    static func refreshAllWidgets() {
        // Tüm uygulama widget'larının yenilenmesi
        WidgetCenter.shared.reloadAllTimelines()
    }

    static func refreshWidget(kind: String) {
        // Belirli bir widget'ın yenilenmesi
        WidgetCenter.shared.reloadTimelines(ofKind: kind)
    }

    // Veri modifikasyonundan sonra uygulamadan çağrı
    static func notifyDataChanged() {
        Task { @MainActor in
            refreshAllWidgets()
        }
    }
}

// 2. Görünüm karmaşıklığını sınırla
struct OptimizedWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        // GeometryReader olmadan basit görünümleri tercih edin
        VStack(alignment: .leading, spacing: 8) {
            ForEach(entry.tasks.prefix(3)) { task in
                // Hafif bileşenler
                minimalTaskRow(task)
            }
        }
        .padding()
    }

    // Performans için minimal görünüm
    @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. Basit durumlar için @AppStorage kullan
struct QuickSettingsWidgetView: View {
    // Paylaşılan UserDefaults'a doğrudan erişim
    @AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
    private var showCompleted = true

    var body: some View {
        // Durum yenilemeler arasında kalıcıdır
        Text(showCompleted ? "Tümünü gösteriyor" : "Tamamlananları gizliyor")
    }
}

// 4. Veriyi provider'da önceden hesapla
struct OptimizedTaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // Önceden hesaplanmış veri
    let completedCount: Int
    let pendingCount: Int
    let highPriorityCount: Int

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

        // Hesaplamalar bir kez yapılır
        self.completedCount = tasks.filter(\.isCompleted).count
        self.pendingCount = tasks.filter { !$0.isCompleted }.count
        self.highPriorityCount = tasks.filter { $0.priority == .high && !$0.isCompleted }.count
    }
}

Bu optimizasyonlar bataryayı aşırı tüketmeyen tepkili widget'lar sağlar.

Widget'ları debug etme

Debug için Xcode'daki widget şemasını kullanın. Canvas önizleme cihaza yükleme olmadan farklı boyutları ve durumları test etmenizi sağlar.

Sonuç

App Intents ile WidgetKit iOS 17+, widget'ları iOS uygulamalarının gerçek etkileşimli uzantılarına dönüştürür. Bu deklaratif mimari, native ve akıcı bir kullanıcı deneyimi sunarken geliştirmeyi önemli ölçüde basitleştirir.

iOS 17+ Etkileşimli Widget Kontrol Listesi

  • ✅ Veri paylaşımı için App Group yapılandırın
  • ✅ Uygun yenileme ile Timeline Provider oluşturun
  • ✅ Her eylem için App Intents uygulayın
  • ✅ Etkileşim için Button(intent:) veya Toggle(intent:) kullanın
  • ✅ Modifikasyondan sonra WidgetCenter.shared.reloadTimelines çağırın
  • ✅ iOS 17+ için zorunlu .containerBackground ekleyin
  • ✅ Akıcı geçiş animasyonları uygulayın
  • ✅ Yükleme ve hata durumlarını yönetin
  • ✅ Görünümleri batarya performansı için optimize edin
  • ✅ Tüm desteklenen widget boyutlarında test edin

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

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

Paylaş

İlgili makaleler