Розробка віджетів для iOS (WidgetKit)
WidgetKit — фреймворк Apple для віджетів на домашньому екрані, екрані блокування та в Standby Mode (iOS 17). Віджети не запускаються як фонові процеси — це статичні снапшоти SwiftUI View, які система оновлює за розписанням через TimelineProvider. Головне архітектурне обмеження: віджет не може отримати дані в момент рендерингу, він лише відображає дані, підготовлені заздалегідь.
Як улаштований WidgetKit всередину
TimelineProvider — ядро віджета
struct MyWidgetProvider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), data: .placeholder)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
completion(SimpleEntry(date: Date(), data: cachedData()))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
Task {
let data = await fetchData()
let entries = buildEntries(from: data)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
}
getTimeline викликається системою за розписанням, не за запитом додатка. Apple не гарантує точність — віджет оновлюється «приблизно» в потрібний час. Віджетам виділяється бюджет оновлень: ~40-70 оновлень на день на всі віджети пристрою. Якщо бюджет виснажений — оновлення відкладаються.
policy: .atEnd — запросити новий timeline коли скінчаться поточні записи. policy: .after(date) — запросити в конкретний час. policy: .never — не запрошувати, лише при явному вызову WidgetCenter.shared.reloadTimelines(ofKind:) з основного додатка.
App Group для передачі даних
Віджет — окремий Extension, не має прямого доступу до даних основного додатка. Спільний контейнер — App Group:
// Додаток пише:
let defaults = UserDefaults(suiteName: "group.com.company.app")
defaults?.set(encodedData, forKey: "widgetData")
// Віджет читає:
let defaults = UserDefaults(suiteName: "group.com.company.app")
let data = defaults?.data(forKey: "widgetData")
FileManager з containerURL(forSecurityApplicationGroupIdentifier:) — для файлів (зображень, баз даних). CoreData з NSPersistentContainer та спільним URL — для складних даних.
Часта помилка: спробувати використовувати Keychain без accessGroup — віджет не отримає доступ до Keychain основного додатка без явної групи.
Розміри та конфігурація
Системні сімейства: .systemSmall, .systemMedium, .systemLarge, .systemExtraLarge (тільки iPad). Екран блокування (iOS 16+): .accessoryCircular, .accessoryRectangular, .accessoryInline. Standby: .systemSmall в режимі Full Screen.
@Environment(\.widgetFamily) всередину View — для умовного рендерингу під різні розміри. Один віджет = один Widget struct; кілька форматів — кілька View по family.
WidgetConfiguration бувает двох видів:
-
StaticConfiguration— без користувацького налаштування -
IntentConfiguration— користувач налаштовує через Siri Intents або App Intents (iOS 17): яке місто, який рахунок, яка крипто
App Intents (iOS 17+) замінює SiriKit Intents для конфігурації віджетів. AppIntentConfiguration + WidgetConfigurationIntent — типобезопасний спосіб без .intentdefinition файлів.
Кейс: віджет для доставки їжі
Віджет показує статус поточного замовлення: «Готується», «В дорозі», «Прибув» з анімацією. Основна складність — частое оновлення (кожні 2-3 хвилини при активному замовленні) виснажує бюджет.
Рішення: Push-повідомлення від бекенду при зміні статусу → додаток отримує background notification → викликає WidgetCenter.shared.reloadAllTimelines(). Віджет оновлюється не за розписанням, а за подією. Бюджет не витрачається.
Для анімації зміни статусу — .contentTransition(.identity) або .contentTransition(.numericText()) у SwiftUI — гладка заміна без миготіння.
Екран блокування та Standby
Accessory віджети (екран блокування) — маленькі, чорно-білі в стандартному стані, з акцентним кольором. widgetAccentable() модифікатор — помітити елемент як «кольоровий» при вклученном акценті.
Standby (iPhone на підставці, iOS 17): віджет .systemSmall показується на весь екран. Додаткова вимога: читаємість з відстані 1-2 метра. Великі шрифти, мінімум деталей.
Термін: 3-5 днів. Залежить від складності конфігурації та необхідності синхронізації даних з бекендом. Вартість розраховується після аналізу вимог.







