Apple планомерно расширяет функционал виджетов с каждым обновлением. Мы уже получили виджеты на Watch и Mac и их разные форматы — на заблокированном экране, live activities. В этом году (WWDC 2023) нам также стали доступны StandBy mode, анимации и возможность управлять виджетом через кнопки.

Хронология развития виджетов в iOS
Хронология развития виджетов в iOS

Меня зовут Даша, я iOS-разработчик в Surf. Сегодня я освежу в вашей памяти философию и design виджетов. Подсвечу технические аспекты, которые помогут, если вы собираетесь делать свой первый виджет или хотите вдохнуть жизнь в старый. 

А ещё затрону несколько неочевидных тем:

  • Взгляд Apple на работу с виджетами.

  • Ценность виджета для пользователя.

  • Внутреннее устройство виджета: данные, методы, свойства.

  • Эффективное обновление виджета.

Философия виджетов 

Для начала посмотрим, как Apple представляют идеальную работу с виджетами. Вот вы берете iPhone в руки, чтобы позалипать в cоцсети и замечаете виджет погоды. Вам достаточно одного взгляда (1), чтобы узнать погоду именно в вашем (2) городе — дождь закончится через 15 минут (3) и выглянет солнце.

Мы видим 3 важных принципа, которые должны быть реализованы в хороших виджетах:

  1. At a glance. Не перегружайте виджет функционалом. Одного взгляда должно быть достаточно для восприятия всей необходимой информации. Если пользователь захочет узнать подробный прогноз погоды на неделю или количество солнечных дней, то просто откроет приложение — это ведь можно сделать одним тапом. 

  2. Персонализация. Данные должны быть интересны конкретному пользователю. Ему нужна погода, которая за его окном, а не чьим-то еще.

  3. Актуальность. Виджет — не большая статичная иконка приложения. Показывайте данные, интересные пользователю в текущий момент времени.

Кстати, при разработке вы заметите, что вся техническая реализация базируется на привязке ко времени.

Тот самый виджет погоды
Тот самый виджет погоды

Как работают виджеты

Давайте быстро пробежимся по технической реализации виджетов:

  1. Вы предоставляете данные в систему.

  2. Система последовательно отрисовывает UI по этим данным и показывает его на главном экране.

  3. Когда данные заканчиваются, система приходит за новыми — возвращаемся к первому пункту.

Теперь обо всем подробнее. 

Timeline

Когда мы говорим о данных, то имеем в виду Timeline. Timeline — массив объектов с привязкой ко времени. Каждый объект — это все данные, необходимые для отрисовки виджета, а также время, когда они станут актуальными. 

Продолжая пример с погодой, объектом будут температура воздуха, осадки и время (!), на которое эта погода будет актуальна. 

Если виджет — View, то Timeline — массив ViewModel, удовлетворяющих протоколу TimelineEntry. Единственное, что жестко требует TimelineEntry — это привязка ко времени. 

struct WeatherEntry: TimelineEntry {
// TimelineEntry
    var date: Date
// Properties
    var temperature: Double
    var isRaining: Bool
}

TimelineProvider

Мы передаем системе Timeline, реализуя TimelineProvider — очередной протокол с методами для генерации timeline’ов. Система же занимается отрисовкой. 

Говоря простыми словами, мы пишем «сценарий», когда и что показывать — timeline. А система, используя UI виджета, рисует «комикс» — набор карточек, которые потом видны пользователю. Система сама следит за временем и обновляет виджет. 

Учтите, что точное попадание во время, которое вы указали в TimelineEntry date, не гарантировано.  

Помимо предоставления timeline, TimelineProvider просит реализовать ещё два метода: 

  • getSnapshot(in:completion:) — возвращает timelineEntry для дальнейшей отрисовки скелетона на случай, если настоящие данные еще не подгрузились. 

  • placeholder(in:) — возвращает timelineEntry для галереи виджетов. Этими данными постарайтесь максимально полно продемонстрировать пользователю возможности вашего виджета.  

TimelineReloadPolicy

Помимо данных для отображения, у Timeline есть одно важное свойство — policy: TimelineReloadPolicy, определяющее, как система будет получать новые данные. Оно представлено в трёх вариантах:

  • atEnd — WidgetKit запросит новый timeline, когда в текущем закончатся данные;

  • after(Date) — WidgetKit запросит новый timeline, когда наступит определенный момент времени;

  • never — WidgetKit оставит на экране последние доступные данные и не будет запрашивать новые. 

Кроме того, Timeline может обновиться принудительно, не дожидаясь своего TimelineReloadPolicy. Нам больше всего интересны два кейса:

  1. Вызов метода WidgetCenter.shared.reloadTimelines(ofKind: “com.weatherwidget.rainstatus") в самом приложении.  

  2. Взаимодействие пользователя с интерактивным виджетом (с iOS 17).

Как эффективно обновлять виджеты

Вы уже могли предположить, что обновление виджета (запрос и расчет новой Timeline) — трудоемкий процесс. Система накладывает ограничения на частоту обновлений, так что делать запрос на апдейт каждую минуту не получится. 

На каждый виджет выделяется «бюджет» — Apple подробно рассказали о нём в документации WidketKit. Если коротко, то количество доступных обновлений в день зависит от «полезности» виджета. Система, например, учитывает:

  • как часто пользователь заглядывает на экран с вашим виджетом; 

  • когда виджет обновлялся последний раз;

  • насколько серьезно изменилась геолокация (если используете её). 

В документации говорится, что в среднем виджет может рассчитывать всего на 40-70 обновлений в день. Стоит хорошо продумать алгоритм обновления данных и выбор подходящей TimelineReloadPolicy, чтобы уложиться в лимит. 

Хорошая новость: обновления виджета из приложения или из-за взаимодействия пользователя не тратят «бюджета». Старайтесь делать большую часть работы — подготовку и обновление данных — тогда, когда приложение находится в foreground. 

Подробный пример оптимизации числа обновлений лежит в документации

Как попасть в Smart Stack

Помимо частоты обновления ещё интересно, как часто наш виджет будут показывать в Smart Stack. 

Пользователь может объединять виджеты в стопки — Smart Stack. Система сама подбирает наиболее актуальный виджет и отображает его первым. Начиная с iOS 15, в Smart Stack  могут рекомендоваться виджеты, которые сам пользователь в стопку не добавлял (Widget Suggestions).  

Smart Stack
Smart Stack

Есть несколько способов помочь системе разобраться в том, насколько виджет полезен пользователю в моменте: 

1. Задать значение relevance у каждой TimelineEntry. Она нужна, чтобы показать, насколько относительно других entries потенциально важна текущая. 

Пример: в течение часа начнется дождь. Пользователь хотел бы узнать об этом до того, как выйдет из дома без зонта. Чтобы ему помочь, для timelineEntry с информацией об осадках поставим relevance.score выше остальных. При прочих равных SmartStack переместит виджет погоды наверх стопки, а пользователь останется сухим.

2. Воспользоваться App Intents. Фреймворк позволяет контенту и функциям приложения интегрироваться с системными службами, такими как Siri и Shortcuts. В контексте виджетов нам он особенно полезен, ведь с App Intents система может сама добавить ваш виджет в стопку. Вот как это работает:

  • Вы «скармливаете» системе информацию о действиях пользователя (intents) внутри приложения.

  • Система, исходя из собранных предпочтений и привычек пользователя, конфигурирует и показывает ваш виджет.

Пример: у вас есть приложение BookAndFilmAdviser — оно подсказывает, что почитать или посмотреть. Соответственно, у него две конфигурации виджета — с книгами и фильмами. Допустим, пользователь каждую пятницу ищет новый фильм на вечер. Получив информацию об этих действиях через App Intents, iOS создает виджет вашего приложения в конфигурации «фильмы» и отправит его наверх стопки в следующую пятницу

Как хорошо задизайнить виджет

О дизайне виджетов есть отличное видео с WWDC. Вот главное, что я из него выделила: 

  1. Следуйте Human Interface Guidelines

  2. Используйте стандартные шрифты. Во-первых, виджет будет сочетаться с другими иконками на Home screen. Во-вторых, обеспечивает автоматическую поддержку Dynamic type. 

  3. Используйте ContainerRelativeShape для скругления углов. 

  4. Тестируйте свой виджет в разных окружениях. 

  5. Не добавляйте заголовок — название приложения по умолчанию присутствует под виджетом. 

  6. Добавляйте иконку только в том случае, если ваше приложение отображает сторонний контент. Как Apple TV или Podcasts.  

В общем, черпайте вдохновение из системных виджетов в iOS. А вот пример хороших не системных виджетов:

Полезные ссылки для вашего виджета — вместо заключения

В конце я бы просто хотела поделиться подборкой полезностей, которые помогут вам создать идеальный виджет. Инструментов и возможностей очень много! 

  1. Вы можете создавать виджеты в разных конфигурациях, как в примере про BooksAndFilmsAdvisor. 

  2. Даже если вы поддерживаете iOS ниже 17, а обрабатывать нажатия пользователя как-то хочется, можно посмотреть на widgetURL. Ну а если вы всё-таки обновили телефончики, то можете порадовать себя свежими анимациями и настоящими кнопками. 

  3. Не забывайте и про адаптацию дизайна под все платформы и окружения — там все не так сложно, как может показаться.

  4. WidgetKit — это не только виджеты, но и Live Activities, Watch Complications.

Надеюсь, всё то, что я собрала для вас, вдохновит на новые идеи и достижения. Скорее садитесь творить! 

Больше полезного про iOS — в телеграм-канале Surf iOS Team. Публикуем кейсы, лучшие практики, новости и вакансии Surf. Присоединяйтесь >>

Бонус тем, кто дочитал до конца

Обновлять виджет каждую секунду не получится, но мы можем отобразить тикающий таймер:

var body: some View {
        VStack(alignment: .leading) {
            Text("Timer").font(.title)
            let components = DateComponents(minute: 5)
            let futureDate = Calendar.current.date(byAdding: components, to: .now)!
            Text(futureDate, style: .timer)
        }
    }

Комментарии (1)


  1. TDMNS
    16.10.2023 06:08

    Мне понравилось, но как мне показалось, суховато. Чего-то не хватает, но понять не могу чего.

    Из того что могу расписать:

    1. материал тяжело читается;

    2. хочется больше конкретики и примеров;

    3. А также хочется полноценного вывода. Литература - литературой, но вот так заменять критично заключение / вывод на литературу, как мне кажется не сильно уместно.
      Я хотел бы увидеть в выводе что-то вроде: "а вот на реальной практике, мы делали то-то и то-то для компании X (не обязательно раскрывать название и выкладывать все карты на стол), и там было вот так и вот так. Заказчику понравилось и все остались довольны, время потеряли (или даже не потеряли?) не зря, советую и рекомендую". Просто сейчас это выглядит как обзор (а если это обзор, то для чего техническая часть? Значит все таки не обзор). И вообще стоит ли двигать это заказчикам с точки зрения развития его бизнеса - не понятно.

    В любом случае автор молодец, тема нетривиальна. Подача понравилась, продолжайте в том же духе, прислушиваясь к читателям.