Tüm Yazılar
KategoriWidgetKit
Okuma Süresi
20 dk
Yayın Tarihi
...
Kelime Sayısı
1.468kelime

Kahveni hazırla - bu içerikli bir makale!

iOS 17'nin en heyecan verici özelliği: Artık widget'lar dokunulabilir! Button, Toggle ve AppIntents ile interaktif widget'lar oluşturun.

iOS 17 Interactive Widgets: Dokunulabilir Widget'lar Yaratın

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

  1. iOS 17 Widget Yenilikleri
  2. Interactive Widget Anatomisi
  3. Button Widget Oluşturma
  4. Toggle Widget
  5. AppIntents Entegrasyonu
  6. Timeline ve Refresh Stratejileri
  7. StandBy Mode Desteği
  8. Widget Konfigürasyonu
  9. Animasyonlar ve Geçişler
  10. Best Practices ve Performans
  11. 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 Elements
2Button(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 WidgetKit
2import SwiftUI
3import AppIntents
4 
5struct TaskEntry: TimelineEntry {
6 let date: Date
7 let tasks: [TaskItem]
8 let configuration: ConfigurationAppIntent
9}
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 AppIntents
2 
3struct CompleteTaskIntent: AppIntent {
4 static var title: LocalizedStringResource = "Görevi Tamamla"
5
6 @Parameter(title: "Görev ID")
7 var taskId: String
8
9 init() {}
10
11 init(taskId: String) {
12 self.taskId = taskId
13 }
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: TaskItem
3
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: Bool
6
7 init() {}
8
9 init(value: Bool) {
10 self.value = value
11 }
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: FocusEntry
22
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: String
6
7 @Parameter(title: "Öncelik")
8 var priority: TaskPriority
9
10 static var parameterSummary: some ParameterSummary {
11 Summary("\(\.$taskName) ekle") {
12 \.$priority
13 }
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: false
22 )
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 Policies
2.atEnd // Tüm entry'ler bitince yeni timeline iste
3.after(date) // Belirli bir tarihten sonra
4.never // Manuel refresh bekle
5 
6// Manual Refresh
7WidgetCenter.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 in
8 TaskWidgetView(entry: entry)
9 .containerBackground(.fill.tertiary, for: .widget)
10 }
11 .supportedFamilies([
12 .systemSmall,
13 .systemMedium,
14 .accessoryCircular,
15 .accessoryRectangular
16 ])
17 }
18}
19 
20// StandBy için optimize edilmiş view
21struct StandByTaskView: View {
22 @Environment(\.widgetFamily) var family
23 let entry: TaskEntry
24
25 var body: some View {
26 if family == .accessoryRectangular {
27 // Kompakt view
28 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: TaskCategory
6
7 @Parameter(title: "Görev Sayısı", default: 5)
8 var taskCount: Int
9
10 @Parameter(title: "Tamamlananları Göster", default: false)
11 var showCompleted: Bool
12}

Animasyonlar ve Geçişler {#animasyonlar}

iOS 17 widget animasyonları:

swift
1struct AnimatedWidgetView: View {
2 let entry: TaskEntry
3
4 var body: some View {
5 VStack {
6 // Sayı değişikliğinde animasyon
7 Text("\(entry.tasks.count)")
8 .font(.system(size: 60, weight: .bold))
9 .contentTransition(.numericText(value: Double(entry.tasks.count)))
10
11 // Symbol animasyonu
12 Image(systemName: entry.allCompleted ? "checkmark.seal.fill" : "checklist")
13 .contentTransition(.symbolEffect(.replace))
14
15 // Liste animasyonu
16 ForEach(entry.tasks) { task in
17 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 perform
2func perform() async throws -> some IntentResult {
3 await DataManager.shared.toggleItem(id: itemId)
4 return .result()
5}
6 
7// ❌ DON'T: Ağır işlem
8func 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 yap
17func 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:

  1. Basit başla - Bir toggle veya button ile
  2. App Intents öğren - Widget, Siri ve Shortcuts için tek kaynak
  3. StandBy düşün - Widget'ın StandBy'da nasıl görüneceğini planla
  4. Performansa dikkat - Intent'ler hızlı olmalı

Kaynaklar


*Widget'lar artık sadece "göster" değil, "yap" da diyor.* 📱

Etiketler

#WidgetKit#iOS 17#SwiftUI#AppIntents#Interactive#StandBy
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz