Розробляючи віджет для iOS, ви неминуче стикаєтеся з обмеженнями WidgetKit. Найчастіша помилка — намагатися оновлювати віджет як звичайний додаток, викликаючи API кожну секунду. WidgetKit — це не фоновий процес, а статичні знімки SwiftUI View, які система оновлює за розкладом. Якщо TimelineProvider налаштований з політикою .after(date) без урахування бюджету оновлень, віджет швидко вичерпає ліміт ~40–70 оновлень на день і перестане оновлюватися до наступного дня. Ми бачили проєкти, де віджети показували дані 4-годинної давності через неправильну конфігурацію. Інша проблема — передача даних з основного додатка: розробники забувають налаштувати App Group і намагаються читати з Keychain без accessGroup, що призводить до падіння віджета. Нижче — технічний розбір того, як уникнути цих пасток і створити віджет, який працює надійно.
Чому WidgetKit — не панацея?
WidgetKit — фреймворк Apple для віджетів на домашньому екрані, екрані блокування та в Standby Mode. Віджети не запускаються як фонові процеси — це статичні снапшоти SwiftUI View, які система оновлює за розкладом через TimelineProvider. Головне архітектурне обмеження: віджет не може отримати дані в момент рендерингу, він лише відображає дані, підготовлені заздалегідь.
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, коли закінчаться поточні записи; .after(date) — запросити в конкретний час; .never — не запитувати, тільки при явному виклику WidgetCenter.shared.reloadTimelines(ofKind:) з основного додатка.
Технічні деталі TimelineProvider
TimelineProvider може бути синхронним або асинхронним. Для асинхронних операцій використовуйте Task всередині getTimeline. Не забувайте кешувати дані — якщо завантаження не вдалося, поверніть попередній timeline з політикою .after(5 minutes).
Як передати дані з додатка у віджет?
Віджет — окремий 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 |
1×1 | Компактне відображення ключового показника | На домашньому екрані та в Standby |
.systemMedium |
2×1 | Список з короткою інформацією | Поширений формат |
.systemLarge |
2×2 | Деталізовані дані | Займає багато місця, вимагає контенту |
.systemExtraLarge |
4×2 (iPad) | Тільки для iPad | Максимальна інформативність |
| Accessory (lock screen) | Дуже малі | На екрані блокування | Чорно-білий фон, accent-колір через .widgetAccentable() |
Статична та конфігурована конфігурація
StaticConfiguration — без користувацького налаштування, підходить для простих віджетів (наприклад, поточна дата). IntentConfiguration — користувач налаштовує через Siri Intents або App Intents: яке місто, який рахунок, яка криптовалюта. App Intents замінює SiriKit Intents для конфігурації віджетів — це типобезпечний спосіб без .intentdefinition файлів.
StaticConfiguration простіший у реалізації, але IntentConfiguration дає користувачеві гнучкість. У наших проєктах IntentConfiguration збільшує залученість у 3 рази порівняно з StaticConfiguration. IntentConfiguration — найкращий вибір для персоналізованих віджетів.
Порівняння методів оновлення віджетів
| Метод | Частота | Витрата бюджету | Застосування |
|---|---|---|---|
| Планове (timeline) | Кожні N хвилин | Високий | Прості віджети з рідкісними змінами |
| По події (push) | Тільки при зміні | Мінімальний | Часто оновлювані дані (статуси, курси) |
| Змішаний | Комбінація | Середній | Гарантоване оновлення + event-driven |
З нашої практики: віджет для доставки їжі
Наш клієнт — сервіс доставки їжі. Задача: показувати статус замовлення на віджеті («Готується», «В дорозі», «Прибув») з плавною анімацією зміни. Основна складність — часте оновлення (кожні 2–3 хвилини при активному замовленні) вичерпувало б бюджет. Кількість замовлень у піку сягала 200 на день, кожне з 4 статусами. Якби віджет оновлювався за розкладом кожні 15 хвилин, він би спалив весь бюджет за 5 годин.
Рішення: push-сповіщення від бекенду при зміні статусу → додаток отримує background notification → викликає WidgetCenter.shared.reloadAllTimelines(). Віджет оновлюється не за розкладом, а по події — бюджет не витрачається. Для анімації зміни статусу використовували .contentTransition(.identity) та .contentTransition(.numericText()) у SwiftUI — плавна заміна без перемиготіння.
Такий підхід економить до 80% бюджету оновлень порівняно з плановим оновленням. Час відгуку віджета після push-сповіщення становить менше 1 секунди. Цей WidgetKit кейс демонструє переваги конфігурованих віджетів (IntentConfiguration) та event-driven оновлення.
Що входить у розробку віджета під ключ?
Етапи роботи:
- Аналіз вимог і вибір розміру (small/medium/large/accessory)
- Проектування Timeline Provider з урахуванням джерела даних
- Налаштування App Group, UserDefaults або CoreData для обміну даними
- Реалізація deep linking (Universal Links) для переходу з віджета в потрібний екран додатка
- Інтеграція push-сповіщень для event-driven оновлень
- Тестування на реальних пристроях з різними версіями iOS
- Підготовка метаданих для App Store Connect (screenshots, description)
- Документація з підтримки та оновлення
Детальніше про TimelineProvider читайте в Apple Developer Documentation.
Екран блокування та Standby
Accessory віджети (екран блокування) — маленькі, чорно-білі в стандартному стані, з акцентним кольором. Модифікатор .widgetAccentable() позначає елемент як «кольоровий» при увімкненому акценті. Standby (iPhone на підставці): віджет .systemSmall показується на весь екран. Додаткова вимога: читабельність з відстані 1–2 метри — великі шрифти та мінімум деталей.
Терміни та вартість
Термін розробки одного віджета — від 3 до 10 днів залежно від складності (наявність конфігурації, синхронізація з бекендом, кастомна анімація). Вартість розробки простого віджета — від $400, складного — до $2500. У нас за плечима понад 5 років досвіду в мобільній розробці та 20+ проєктів з WidgetKit. Замовте розробку віджета під ключ — зв'яжіться з нами, і ми розрахуємо точний обсяг робіт. Отримайте консультацію інженера.







