iOS 14'te widget'lar geldiğinde heyecanlandık. Ama bir şey eksikti: etkileşim. Widget'a dokununca sadece uygulamaya gidiyordu. iOS 17 ile bu değişti.
Artık widget'lara buton ekleyebilir, toggle yapabilir, hatta form doldurabilirsin. Bu rehberde, interaktif widget'ların tüm sırlarını öğreneceksin.
İçindekiler
- iOS 17 Widget Yenilikleri
- Interactive Widget Anatomisi
- Button Widget Oluşturma
- Toggle Widget
- AppIntents Entegrasyonu
- Timeline ve Refresh Stratejileri
- StandBy Mode Desteği
- Widget Konfigürasyonu
- Animasyonlar ve Geçişler
- Best Practices ve Performans
- Sonuç ve Öneriler
iOS 17 Widget Yenilikleri {#ios17-yenilikler}
iOS 17, widget'ları tamamen dönüştürdü:
Özellik | iOS 16 | iOS 17 |
|---|---|---|
**Etkileşim** | Sadece deep link | Button, Toggle, Form |
**Animasyon** | Yok | contentTransition, animation |
**StandBy** | Yok | Tam destek |
**Intent** | Siri Intent | App Intent (yeni!) |
**Refresh** | Timeline only | Interactive + Timeline |
Desteklenen Etkileşim Türleri
swift
1// iOS 17+ Interactive Elements2Button(intent: MyIntent()) { Label } // ✅3Toggle(isOn: intent) { Label } // ✅4Link(destination: url) { Label } // ✅ (deep link)5 6// Desteklenmeyen (henüz)7TextField // ❌8Slider // ❌9Picker // ❌Interactive Widget Anatomisi {#widget-anatomisi}
Bir interactive widget şu bileşenlerden oluşur:
- Widget Extension (Widget.swift, Provider.swift, Entry.swift, Views)
- App Intents Extension (Intent'ler)
- Shared (App Group ile data paylaşımı)
Temel Setup
swift
1import WidgetKit2import SwiftUI3import AppIntents4 5struct TaskEntry: TimelineEntry {6 let date: Date7 let tasks: [TaskItem]8 let configuration: ConfigurationAppIntent9}10 11struct TaskWidgetProvider: AppIntentTimelineProvider {12 13 func placeholder(in context: Context) -> TaskEntry {14 TaskEntry(15 date: Date(),16 tasks: TaskItem.sampleTasks,17 configuration: ConfigurationAppIntent()18 )19 }20 21 func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> TaskEntry {22 let tasks = await TaskManager.shared.getTasks(limit: 5)23 return TaskEntry(date: Date(), tasks: tasks, configuration: configuration)24 }25 26 func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<TaskEntry> {27 let tasks = await TaskManager.shared.getTasks(limit: 5)28 let entry = TaskEntry(date: Date(), tasks: tasks, configuration: configuration)29 30 let nextUpdate = Calendar.current.date(byAdding: .minute, value: 30, to: Date())!31 return Timeline(entries: [entry], policy: .after(nextUpdate))32 }33}Button Widget Oluşturma {#button-widget}
En basit etkileşim: Button ile aksiyon tetikleme.
swift
1import AppIntents2 3struct CompleteTaskIntent: AppIntent {4 static var title: LocalizedStringResource = "Görevi Tamamla"5 6 @Parameter(title: "Görev ID")7 var taskId: String8 9 init() {}10 11 init(taskId: String) {12 self.taskId = taskId13 }14 15 func perform() async throws -> some IntentResult {16 await TaskManager.shared.completeTask(id: taskId)17 WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")18 return .result()19 }20}Widget View with Button
swift
1struct TaskRow: View {2 let task: TaskItem3 4 var body: some View {5 HStack(spacing: 12) {6 // Interactive Button!7 Button(intent: CompleteTaskIntent(taskId: task.id)) {8 Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")9 .font(.title2)10 .foregroundStyle(task.isCompleted ? .green : .secondary)11 }12 .buttonStyle(.plain)13 14 VStack(alignment: .leading) {15 Text(task.title)16 .strikethrough(task.isCompleted)17 18 if let dueDate = task.dueDate {19 Text(dueDate, style: .relative)20 .font(.caption2)21 }22 }23 }24 .contentTransition(.numericText())25 }26}💡 Pro Tip: .buttonStyle(.plain) kullanmayı unutma! Aksi halde widget'ın tamamı tıklanabilir olur.
Toggle Widget {#toggle-widget}
Toggle, boolean state için ideal:
swift
1struct ToggleFocusModeIntent: AppIntent, SetValueIntent {2 static var title: LocalizedStringResource = "Odak Modunu Değiştir"3 4 @Parameter(title: "Aktif")5 var value: Bool6 7 init() {}8 9 init(value: Bool) {10 self.value = value11 }12 13 func perform() async throws -> some IntentResult {14 await FocusManager.shared.setFocusMode(enabled: value)15 WidgetCenter.shared.reloadTimelines(ofKind: "FocusWidget")16 return .result()17 }18}19 20struct FocusWidgetView: View {21 let entry: FocusEntry22 23 var body: some View {24 VStack(spacing: 16) {25 Image(systemName: entry.isFocusModeOn ? "moon.fill" : "moon")26 .font(.system(size: 40))27 .contentTransition(.symbolEffect(.replace))28 29 Text(entry.isFocusModeOn ? "Odak Modu Açık" : "Kapalı")30 31 // Interactive Toggle!32 Toggle(33 isOn: entry.isFocusModeOn,34 intent: ToggleFocusModeIntent(value: !entry.isFocusModeOn)35 ) {36 EmptyView()37 }38 .toggleStyle(.switch)39 }40 }41}AppIntents Entegrasyonu {#app-intents}
AppIntents, widget ve Siri/Shortcuts arasında köprü kurar:
swift
1struct AddTaskIntent: AppIntent {2 static var title: LocalizedStringResource = "Görev Ekle"3 4 @Parameter(title: "Görev Adı", requestValueDialog: "Görev adı ne olsun?")5 var taskName: String6 7 @Parameter(title: "Öncelik")8 var priority: TaskPriority9 10 static var parameterSummary: some ParameterSummary {11 Summary("\(\.$taskName) ekle") {12 \.$priority13 }14 }15 16 func perform() async throws -> some IntentResult & ProvidesDialog {17 let task = TaskItem(18 id: UUID().uuidString,19 title: taskName,20 priority: priority,21 isCompleted: false22 )23 24 await TaskManager.shared.addTask(task)25 WidgetCenter.shared.reloadAllTimelines()26 27 return .result(dialog: "\(taskName) eklendi!")28 }29}30 31enum TaskPriority: String, AppEnum {32 case low = "Düşük"33 case medium = "Orta"34 case high = "Yüksek"35 36 static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Öncelik")37 38 static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [39 .low: DisplayRepresentation(title: "Düşük"),40 .medium: DisplayRepresentation(title: "Orta"),41 .high: DisplayRepresentation(title: "Yüksek")42 ]43}Timeline ve Refresh Stratejileri {#timeline-refresh}
Widget'ların doğru zamanda güncellenmesi kritik:
swift
1// Timeline Policies2.atEnd // Tüm entry'ler bitince yeni timeline iste3.after(date) // Belirli bir tarihten sonra4.never // Manuel refresh bekle5 6// Manual Refresh7WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")8WidgetCenter.shared.reloadAllTimelines()⚠️ Dikkat: Apple günlük refresh limitini takip eder! Çok sık refresh isteme.
StandBy Mode Desteği {#standby-mode}
iOS 17'de iPhone'u yatay koyduğunda StandBy mode açılır:
swift
1struct TaskWidget: Widget {2 var body: some WidgetConfiguration {3 AppIntentConfiguration(4 kind: "TaskWidget",5 intent: ConfigurationAppIntent.self,6 provider: TaskWidgetProvider()7 ) { entry in8 TaskWidgetView(entry: entry)9 .containerBackground(.fill.tertiary, for: .widget)10 }11 .supportedFamilies([12 .systemSmall,13 .systemMedium,14 .accessoryCircular,15 .accessoryRectangular16 ])17 }18}19 20// StandBy için optimize edilmiş view21struct StandByTaskView: View {22 @Environment(\.widgetFamily) var family23 let entry: TaskEntry24 25 var body: some View {26 if family == .accessoryRectangular {27 // Kompakt view28 HStack {29 Text("\(entry.tasks.filter { !$0.isCompleted }.count)")30 .font(.largeTitle)31 Text("görev")32 .font(.caption)33 }34 .widgetAccentable()35 } else {36 TaskWidgetView(entry: entry)37 }38 }39}Widget Konfigürasyonu {#widget-konfigurasyon}
Kullanıcının widget'ı özelleştirmesine izin ver:
swift
1struct ConfigurationAppIntent: WidgetConfigurationIntent {2 static var title: LocalizedStringResource = "Widget Ayarları"3 4 @Parameter(title: "Kategori", default: .all)5 var category: TaskCategory6 7 @Parameter(title: "Görev Sayısı", default: 5)8 var taskCount: Int9 10 @Parameter(title: "Tamamlananları Göster", default: false)11 var showCompleted: Bool12}Animasyonlar ve Geçişler {#animasyonlar}
iOS 17 widget animasyonları:
swift
1struct AnimatedWidgetView: View {2 let entry: TaskEntry3 4 var body: some View {5 VStack {6 // Sayı değişikliğinde animasyon7 Text("\(entry.tasks.count)")8 .font(.system(size: 60, weight: .bold))9 .contentTransition(.numericText(value: Double(entry.tasks.count)))10 11 // Symbol animasyonu12 Image(systemName: entry.allCompleted ? "checkmark.seal.fill" : "checklist")13 .contentTransition(.symbolEffect(.replace))14 15 // Liste animasyonu16 ForEach(entry.tasks) { task in17 TaskRow(task: task)18 .transition(.push(from: .leading))19 }20 }21 .animation(.smooth, value: entry.tasks.count)22 }23}💡 Pro Tip: Widget animasyonları otomatik olarak 2 saniyeyle sınırlıdır!
Best Practices ve Performans {#best-practices}
Do's and Don'ts
swift
1// ✅ DO: Lightweight intent perform2func perform() async throws -> some IntentResult {3 await DataManager.shared.toggleItem(id: itemId)4 return .result()5}6 7// ❌ DON'T: Ağır işlem8func perform() async throws -> some IntentResult {9 let data = try await URLSession.shared.data(from: url) // YAPMA!10 return .result()11}12 13// ✅ DO: App Group ile data paylaş14let sharedDefaults = UserDefaults(suiteName: "group.com.myapp")15 16// ✅ DO: Placeholder'ı gerçekçi yap17func placeholder(in context: Context) -> TaskEntry {18 TaskEntry(19 date: Date(),20 tasks: [21 TaskItem(id: "1", title: "Örnek görev...", isCompleted: false),22 TaskItem(id: "2", title: "Başka bir görev", isCompleted: true),23 ]24 )25}Memory Limits
- Small: ~16 MB
- Medium: ~30 MB
- Large: ~40 MB
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Sonuç ve Öneriler {#sonuc-ve-oneriler}
iOS 17 interactive widget'lar, kullanıcı deneyimini tamamen dönüştürüyor:
- Basit başla - Bir toggle veya button ile
- App Intents öğren - Widget, Siri ve Shortcuts için tek kaynak
- StandBy düşün - Widget'ın StandBy'da nasıl görüneceğini planla
- Performansa dikkat - Intent'ler hızlı olmalı
Kaynaklar
- Apple Developer:: [Making Your App Content Interactive in Widgets](https://developer.apple.com/documentation/widgetkit/making-your-app-content-interactive-in-widgets)
- Apple Developer:: [App Intents Documentation](https://developer.apple.com/documentation/appintents)
- WWDC 2023:: [Bring widgets to new places](https://developer.apple.com/videos/play/wwdc2023/10027/)
*Widget'lar artık sadece "göster" değil, "yap" da diyor.* 📱

