Разработка 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 недели. Стоимость рассчитывается после анализа данных и частоты обновления.







