Разработка виджетов для 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 mode.
@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 дней. Зависит от сложности конфигурации и необходимости синхронизации данных с бэкендом. Стоимость рассчитывается после анализа требований.







