WidgetKit iOS 17-26: App Intents ile Etkileşimli Widget'lar
WidgetKit ve App Intents ile etkileşimli iOS widget'ları oluşturmak için kapsamlı kılavuz. Düğmeler, anahtarlar, animasyonlar, iOS 26 Liquid Glass materyali ve WWDC 2026 güncellemelerini kapsar.

iOS 17, WidgetKit'e ana ekrandan doğrudan kullanıcı eylemlerine yanıt veren native etkileşim getirdi. iOS 26, Liquid Glass materyali ve akcentli render modları ile bu deneyimi geliştirdi. Bu kılavuz, iOS 17'den iOS 26'ya kadar etkileşimli widget geliştirmesini kapsar.
iOS 17-26 etkileşimli widget'ları oluşturma, proje kurulumundan animasyonlar, durum yönetimi ve en son WWDC 2026 App Intents özellikleri ile gelişmiş kalıplara kadar.
iOS 17+ Etkileşimli Widget Mimarisi
iOS 17+ widget 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.
import WidgetKit
import SwiftUI
import AppIntents
// The architecture relies on three main components:
// 1. Widget Timeline Provider - supplies the data
// 2. Widget View - displays the interface with Button/Toggle
// 3. App Intent - executes the action on tap
struct TaskWidget: Widget {
// Unique widget identifier
let kind: String = "TaskWidget"
var body: some WidgetConfiguration {
// StaticConfiguration for widgets without parameters
StaticConfiguration(
kind: kind,
provider: TaskTimelineProvider()
) { entry in
TaskWidgetView(entry: entry)
// Required for App Intents since iOS 17
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Tasks")
.description("Manage your tasks from the home screen.")
.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 ve iOS 26 uygun olduğunda Liquid Glass stilini otomatik olarak uygular.
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.
import WidgetKit
import SwiftUI
// Entry representing widget state at a given moment
struct TaskEntry: TimelineEntry {
let date: Date
let tasks: [Task]
// Loading state for visual feedback
var isLoading: Bool = false
}
// Data model shared between app and 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 displayed during initial loading
func placeholder(in context: Context) -> TaskEntry {
TaskEntry(
date: Date(),
tasks: [
Task(id: UUID(), title: "Sample task", isCompleted: false, priority: .medium)
]
)
}
// Snapshot for widget gallery
func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
let entry = TaskEntry(
date: Date(),
tasks: TaskDataManager.shared.fetchTasks().prefix(3).map { $0 }
)
completion(entry)
}
// Complete timeline with refresh policy
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 in 15 minutes or after user action
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. İlgili durum yönetimi kalıpları için SwiftUI @Observable vs @State makalesine bakılabilir.
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.
import Foundation
final class TaskDataManager {
// Singleton for global access
static let shared = TaskDataManager()
// App Group identifier configured in Xcode
private let appGroupID = "group.com.example.taskapp"
// UserDefaults shared between app and widget
private var sharedDefaults: UserDefaults? {
UserDefaults(suiteName: appGroupID)
}
private let tasksKey = "tasks"
private init() {}
// Fetch tasks from shared storage
func fetchTasks() -> [Task] {
guard let data = sharedDefaults?.data(forKey: tasksKey),
let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
return []
}
return tasks
}
// Save with widget notification
func saveTasks(_ tasks: [Task]) {
guard let data = try? JSONEncoder().encode(tasks) else { return }
sharedDefaults?.set(data, forKey: tasksKey)
}
// Update a specific task
func updateTask(_ task: Task) {
var tasks = fetchTasks()
if let index = tasks.firstIndex(where: { $0.id == task.id }) {
tasks[index] = task
saveTasks(tasks)
}
}
// Toggle completed state
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.
import AppIntents
import WidgetKit
// Intent to toggle task state
struct ToggleTaskIntent: AppIntent {
// Title displayed in Siri shortcuts
static var title: LocalizedStringResource = "Toggle task state"
// Description for accessibility
static var description = IntentDescription("Marks a task as completed or not completed.")
// Parameter: ID of task to modify
@Parameter(title: "Task ID")
var taskID: String
// Required initializer for AppIntent
init() {}
// Initializer with parameter for view creation
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// Action execution
func perform() async throws -> some IntentResult {
// Convert string ID to UUID
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
// Update the task
TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)
// Request widget refresh
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.
import SwiftUI
import WidgetKit
struct TaskWidgetView: View {
let entry: TaskEntry
// Adapt to widget size
@Environment(\.widgetFamily) var family
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// Header with title and counter
headerView
// Task list with interactive buttons
ForEach(entry.tasks.prefix(tasksLimit)) { task in
TaskRowView(task: task)
}
Spacer(minLength: 0)
}
.padding()
}
// Number of tasks based on size
private var tasksLimit: Int {
switch family {
case .systemSmall: return 2
case .systemMedium: return 3
default: return 4
}
}
private var headerView: some View {
HStack {
Text("Tasks")
.font(.headline)
.fontWeight(.bold)
Spacer()
// Badge with remaining task count
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 {
// Button with App Intent as action
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack(spacing: 12) {
// Completion indicator
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundStyle(task.isCompleted ? .green : .secondary)
// Task title
Text(task.title)
.font(.subheadline)
.strikethrough(task.isCompleted)
.foregroundStyle(task.isCompleted ? .secondary : .primary)
.lineLimit(1)
Spacer()
// Priority indicator
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.
import SwiftUI
import AppIntents
// Specific intent for Toggle with explicit state
struct SetTaskCompletionIntent: AppIntent {
static var title: LocalizedStringResource = "Set task state"
@Parameter(title: "Task ID")
var taskID: String
// Target state: true = completed, false = not completed
@Parameter(title: "Completed")
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 }) {
// Set state explicitly (not 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()
// Interactive toggle with intent
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.
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.
import SwiftUI
import WidgetKit
struct AnimatedTaskRowView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack(spacing: 12) {
// Icon with transition animation
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundStyle(task.isCompleted ? .green : .secondary)
// Icon animation on change
.contentTransition(.symbolEffect(.replace))
Text(task.title)
.font(.subheadline)
.strikethrough(task.isCompleted)
.foregroundStyle(task.isCompleted ? .secondary : .primary)
// Text animation
.contentTransition(.opacity)
Spacer()
}
.padding(.vertical, 6)
.padding(.horizontal, 10)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(task.isCompleted ? Color.green.opacity(0.1) : Color.clear)
)
// Background animation
.animation(.easeInOut(duration: 0.3), value: task.isCompleted)
}
.buttonStyle(.plain)
}
}
// Widget with animated invalidation
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("Animated Tasks")
.description("Widgets with smooth animations.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
// Enable content animations
.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("Tasks")
.font(.headline.bold())
Spacer()
let completed = entry.tasks.filter(\.isCompleted).count
let total = entry.tasks.count
// Animated progress
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.
iOS 26 Liquid Glass ve Akcentli Render
iOS 26, widget'lar için Liquid Glass materyali ve akcentli render modları getirdi. Bu görsel güncellemeler .containerBackground kullanıldığında otomatik olarak uygulanır, ancak geliştiriciler render davranışını kontrol edebilir.
import SwiftUI
import WidgetKit
struct ModernTaskWidgetView: View {
let entry: TaskEntry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(entry.tasks.prefix(3)) { task in
TaskRowView(task: task)
}
}
.padding()
// iOS 26: Control accented rendering for images
.widgetAccentedRenderingMode(.fullColor)
}
}
// Extra large portrait family (iOS 26+)
struct LargePortraitTaskWidget: Widget {
let kind: String = "LargePortraitTaskWidget"
var body: some WidgetConfiguration {
StaticConfiguration(
kind: kind,
provider: TaskTimelineProvider()
) { entry in
LargePortraitTaskWidgetView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Task Overview")
.description("Full task overview with extra space.")
// New in iOS 26: system extra large portrait
.supportedFamilies([.systemExtraLarge])
}
}
struct LargePortraitTaskWidgetView: View {
let entry: TaskEntry
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("All Tasks")
.font(.title2.bold())
// More space for task details
ForEach(entry.tasks) { task in
DetailedTaskRowView(task: task)
}
Spacer(minLength: 0)
}
.padding()
}
}
struct DetailedTaskRowView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskIntent(taskID: task.id)) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isCompleted ? .green : .secondary)
Text(task.title)
.font(.headline)
.strikethrough(task.isCompleted)
}
Text("Priority: \(task.priority.rawValue.capitalized)")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(.systemBackground).opacity(0.3))
.cornerRadius(12)
}
.buttonStyle(.plain)
}
}.widgetAccentedRenderingMode(.fullColor) modifier'ı aksentli ortamlarda görüntü renklerini korur. visionOS 26'dan gelen .systemExtraLarge ailesi artık iOS ve iPadOS'ta da kullanılabilir.
WWDC 2026 App Intents: Performans için EntityCollection
WWDC 2026, çok sayıda varlığı verimli bir şekilde işlemek için EntityCollection özelliğini tanıttı. Intent yürütülmeden önce tüm varlıkları çözümlemek yerine, yalnızca tanımlayıcılar iletilir.
import AppIntents
import WidgetKit
// Mark multiple tasks complete at once - efficiently
struct BatchCompleteTasksIntent: AppIntent {
static var title: LocalizedStringResource = "Complete multiple tasks"
// EntityCollection passes IDs only, not resolved entities
@Parameter(title: "Tasks")
var tasks: EntityCollection<TaskEntity>
init() {}
init(taskIDs: [UUID]) {
// Create collection from IDs
self.tasks = EntityCollection(taskIDs.map { TaskEntity(id: $0) })
}
func perform() async throws -> some IntentResult {
// Access IDs directly without resolution overhead
let taskIDs = tasks.identifiers
var allTasks = TaskDataManager.shared.fetchTasks()
for id in taskIDs {
if let uuid = UUID(uuidString: id),
let index = allTasks.firstIndex(where: { $0.id == uuid }) {
allTasks[index].isCompleted = true
}
}
TaskDataManager.shared.saveTasks(allTasks)
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// Entity conformance for TaskEntity
struct TaskEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Task"
static var defaultQuery = TaskEntityQuery()
var id: UUID
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "Task \(id.uuidString.prefix(8))")
}
}
struct TaskEntityQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
identifiers.map { TaskEntity(id: $0) }
}
}Bu kalıp toplu işlemler için kritiktir. Apple'ın dahili testleri, EntityCollection ile 1000 fotoğrafı etiketlemenin yavaştan "neredeyse anlık" hale geldiğini gösterdi.
İlerleme Gösterimli Uzun Süreli Intent'ler
WWDC 2026 ayrıca standart 30 saniyelik limiti aşan işlemler için, ilerlemenin Live Activity olarak görüntülendiği LongRunningIntent özelliğini tanıttı.
import AppIntents
import WidgetKit
struct SyncAllTasksIntent: LongRunningIntent, CancellableIntent {
static var title: LocalizedStringResource = "Sync all tasks"
func perform() async throws -> some IntentResult & ProvidesDialog {
let result = try await performBackgroundTask {
let tasks = TaskDataManager.shared.fetchTasks()
progress.totalUnitCount = Int64(tasks.count)
for (index, task) in tasks.enumerated() {
// Check for cancellation
try Task.checkCancellation()
// Simulate server sync
try await Task.sleep(for: .milliseconds(100))
progress.completedUnitCount = Int64(index + 1)
}
return "Synced \(tasks.count) tasks"
} onCancel: { reason in
// Cleanup based on cancellation reason
switch reason {
case .user:
print("User cancelled sync")
case .timeout:
print("System timeout")
case .systemReclaim:
print("System reclaimed resources")
@unknown default:
break
}
}
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result(dialog: "\(result)")
}
}İlerleme Live Activity olarak görünür ve CancellableIntent iptal nedenini sağlayarak uygun temizlik yapılmasına olanak tanır. Swift eşzamanlılık kalıpları hakkında daha fazla bilgi için Swift Structured Concurrency makalesine bakılabilir.
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.
import WidgetKit
import SwiftUI
import AppIntents
// Configuration exposed to user
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Task configuration"
static var description = IntentDescription("Customize task display.")
// Filter by priority
@Parameter(title: "Priority", default: .all)
var priorityFilter: PriorityFilter
// Show completed tasks
@Parameter(title: "Show completed", default: true)
var showCompleted: Bool
// Maximum number of tasks
@Parameter(title: "Task count", default: 3)
var maxTasks: Int
}
// Enum for priority filter
enum PriorityFilter: String, AppEnum {
case all
case high
case medium
case low
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priority"
static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
.all: "All",
.high: "High",
.medium: "Medium",
.low: "Low"
]
}
// Provider adapted to configuration
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))
}
// Apply configuration filters
private func filteredTasks(for config: TaskWidgetConfigurationIntent) -> [Task] {
var tasks = TaskDataManager.shared.fetchTasks()
// Filter by priority
if config.priorityFilter != .all {
let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
tasks = tasks.filter { $0.priority == priority }
}
// Filter completed if needed
if !config.showCompleted {
tasks = tasks.filter { !$0.isCompleted }
}
// Limit count
return Array(tasks.prefix(config.maxTasks))
}
}
// Widget with user configuration
struct ConfigurableTaskWidget: Widget {
let kind: String = "ConfigurableTaskWidget"
var body: some WidgetConfiguration {
// AppIntentConfiguration for configurable widgets
AppIntentConfiguration(
kind: kind,
intent: TaskWidgetConfigurationIntent.self,
provider: ConfigurableTaskProvider()
) { entry in
TaskWidgetView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Custom Tasks")
.description("Filter and customize your tasks.")
.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. App Intents ve Siri entegrasyonu hakkında daha fazla bilgi için App Intents 2.0 ve Siri Shortcuts makalesine bakılabilir.
En İyi Uygulamalar ve Optimizasyonlar
Birkaç kalıp performanslı ve güvenilir etkileşimli widget'lar sağlar.
import WidgetKit
import SwiftUI
// 1. Always invalidate cache after modification
final class WidgetRefreshManager {
static func refreshAllWidgets() {
// Refresh all app widgets
WidgetCenter.shared.reloadAllTimelines()
}
static func refreshWidget(kind: String) {
// Refresh specific widget
WidgetCenter.shared.reloadTimelines(ofKind: kind)
}
// Call from app after data modification
static func notifyDataChanged() {
Task { @MainActor in
refreshAllWidgets()
}
}
}
// 2. Limit view complexity
struct OptimizedWidgetView: View {
let entry: TaskEntry
var body: some View {
// Prefer simple views without GeometryReader
VStack(alignment: .leading, spacing: 8) {
ForEach(entry.tasks.prefix(3)) { task in
// Lightweight components
minimalTaskRow(task)
}
}
.padding()
}
// Minimal view for 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. Use @AppStorage for simple state
struct QuickSettingsWidgetView: View {
// Direct access to shared UserDefaults
@AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
private var showCompleted = true
var body: some View {
// State persists between refreshes
Text(showCompleted ? "Showing all" : "Hiding completed")
}
}
// 4. Pre-calculate data in provider
struct OptimizedTaskEntry: TimelineEntry {
let date: Date
let tasks: [Task]
// Pre-calculated data
let completedCount: Int
let pendingCount: Int
let highPriorityCount: Int
init(date: Date, tasks: [Task]) {
self.date = date
self.tasks = tasks
// Calculations performed once
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.
Debug için Xcode'daki widget şemasını kullanın. Canvas önizleme cihaza yükleme olmadan farklı boyutları ve durumları test etmenizi sağlar.
Kaynaklar
- WidgetKit Dokümantasyonu - Apple'ın resmi WidgetKit referansı
- App Intents Framework - Tam App Intents API dokümantasyonu
- WWDC 2026: Discover new capabilities in the App Intents framework - EntityCollection, LongRunningIntent, SyncableEntity
- Human Interface Guidelines: Materials - Liquid Glass tasarım kılavuzu
Etkileşimli iOS Widget Geliştirme Kontrol Listesi
App Intents ile WidgetKit, widget'ları iOS uygulamalarının gerçek etkileşimli uzantılarına dönüştürür. iOS 17'de tanıtılan mimari, iOS 26'ya kadar stabil kalır ve görsel iyileştirmeler ile performans özellikleri eklenir.
Uygulama kontrol listesi
- Uygulama ve widget extension arasında veri paylaşımı için App Group yapılandırın
- Uygun yenileme politikası ile Timeline Provider oluşturun
- Her kullanıcı eylemi için App Intents uygulayın
- Etkileşim için
Button(intent:)veyaToggle(intent:)kullanın - Veri değişikliğinden sonra
WidgetCenter.shared.reloadTimelinesçağırın - iOS 17+ uyumluluğu için zorunlu
.containerBackgroundekleyin - iOS 26 Liquid Glass desteği için
.widgetAccentedRenderingModekullanın .contentTransitionile akıcı geçiş animasyonları uygulayın- Görsel geri bildirim için yükleme ve hata durumlarını yönetin
- Çözümleme yükünü önlemek için toplu işlemlerde
EntityCollectionkullanın .systemExtraLargedahil tüm desteklenen widget boyutlarında test edin
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
iOS kodundaki hatayı bulabilir misin?
Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Yazan:
Anthony Fillion-MailletSharpSkill kurucusu
10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.
24 Ağustos 2026 tarihinde güncellendi
Etiketler
Paylaş
İlgili makaleler

App Intents 2.0 ve Siri Shortcuts: iOS 27 Otomasyon Rehberi
iOS 27 için App Intents 2.0 ve Siri Shortcuts hakkında eksiksiz rehber. Streaming responses, çok turlu konuşmalar, View Annotations oluşturma ve Foundation Models entegrasyonu.

Swift'te Combine vs async/await: Aşamalı Geçiş Desenleri
Swift'te Combine'dan async/await'e geçiş için kapsamlı kılavuz: aşamalı stratejiler, köprüleme desenleri ve iOS kod tabanlarında paradigma birlikte yaşamı.

2026'da iOS Erişilebilirlik Mülakat Soruları: VoiceOver ve Dynamic Type
iOS mülakatlarına hazırlık için kritik erişilebilirlik soruları: VoiceOver, Dynamic Type, semantik trait'ler ve denetimler.