Розробка Today Extension (Widget) для iOS
Today Extension — це старий механізм віджетів для Notification Center (Центру сповіщень), доступний з iOS 8. З iOS 14 Apple представила WidgetKit — новий API для віджетів на домашній сторінці та екрані блокування. Today Extension технічно ще працює, але Apple явно направляє розробників у сторону WidgetKit. Якщо мета — віджет на домашній сторінці, це WidgetKit. Якщо потрібна зворотна сумісність з iOS 13 — Today Extension.
WidgetKit: як це устроєно всередину
WidgetKit — не View, який оновлюється в реальному часі. Це снапшоти — заранее отрендовані стани, які система показує за розкладом.
Додаток надає TimelineProvider, який повертає масив TimelineEntry — кожна з датою показу та даними. Система рендерить SwiftUI View для кожного entry заранее і переключає їх за часом. Живого коду у віджеті немає — тільки статичні снапшоти.
Це ламає звичне мислення. Не можна зробити таймер зворотного відліку через Timer — потрібно використовувати Text(..<deadline>, style: .timer), який система сама оновлює нативно. Не можна оновлювати віджет за push-сповіщенням без WidgetKit API.
Оновлення віджета. WidgetCenter.shared.reloadTimelines(ofKind:) — викликається з основного додатку або Background App Refresh. Але система виділяє обмежений бюджет на фонові оновлення (приблизно 40-70 разів на день). Якщо TimelineProvider.getTimeline() запитує оновлення надто часто через TimelineReloadPolicy.after(Date) — система просто ігнорує це і оновлює рідше.
AppIntents та інтерактивні віджети (iOS 17)
З iOS 17 віджети підтримують інтерактивність через AppIntents. Кнопки і Toggle прямо у віджеті виконують AppIntent.perform() без відкриття додатку.
Реалізація: структура, яка відповідає протоколу AppIntent, декорована @MainActor. У SwiftUI віджета — Button(intent: MyIntent()) або Toggle(isOn: binding, intent: MyToggleIntent()).
Обмеження: у віджеті не можна показувати Alert або Sheet. Тільки прості дії. Для складної взаємодії — deep link в основний додаток через .widgetURL() або Link().
App Group та дані віджета
Віджет — окремий Extension process. Дані основного додатку недоступні напряму. Єдиний шлях: App Group.
UserDefaults(suiteName: "group.com.yourapp") — для невеликих даних (флаги, числа, невеликі рядки). FileManager.containerURL(forSecurityApplicationGroupIdentifier:) — для файлів, JSON, зображень.
Часта помилка: зберігати зображення для віджета в звичайному Documents основного додатку. Віджет не бачить цей шлях. Всі ресурси для віджета — тільки в App Group container.
Розміри та екран блокування
WidgetKit підтримує: systemSmall (2×2), systemMedium (4×2), systemLarge (4×4), systemExtraLarge (iPad, 8×2). iOS 16+: accessoryCircular, accessoryRectangular, accessoryInline — для екрана блокування та циферблата Apple Watch.
Віджет для екрана блокування (accessoryCircular) завжди рендерится у grayscale в ambient режимі годинника. Кольори працюють тільки на екрані блокування iPhone. Дизайн повинен добре виглядати в обох варіантах.
Today Extension (legacy)
NCWidgetProviding.widgetPerformUpdate(completionHandler:) — точка обновлення. Extension повинен викликати completionHandler(.newData) або .noData протягом 30 секунд. Якщо не викликав — система вважає Extension зависшим.
Розмір: компактний (фіксований) та розширений через preferredContentSize. UIKit, без SwiftUI.
Розклад
WidgetKit віджет (один розмір, статичні дані): 2–3 тижні. Віджет з інтерактивністю (iOS 17 AppIntents), кілька розмірів, екран блокування: 4–7 тижнів. Today Extension legacy: 2–4 тижні. Вартість розраховується після аналізу даних та частоти оновлення.







