iOS 10: Notification Content Extension
В этой статье речь пойдет о новой возможности в iOS 10 — Notification Content Extension. Это разновидность расширения, которая позволяет отображать пользователю собственный интерфейс при взаимодействии с уведомлением (remote или local). И отдельно коснемся того, что можно, а что нельзя делать в этом новом расширении — в том числе насколько оно гибко настраивается и конфигурируется.
Notification Content Extension позволяет взаимодействовать с приложением, формально не запуская его. Наибольшую продуктивность это расширение приносит, если от пользователя требуется одно или несколько простых действий в ответ на принятое уведомление. В последней beta-версии возможность взаимодействия пользователей с такими уведомлениями не ограничивается устройствами с поддержкой 3D Touch, на старых устройствах экран расширения покажется при жесте вниз на баннере уведомления.
Разберем в деталях этот новый механизм на тестовом проекте, с которым вы можете ознакомиться тут:
- Добавление target для extension в проект.
- Проектирование и стилизация интерфейса.
- Добавление действий и их обработка.
- Ограничения по кастомизации интерфейса.
Добавление target для extension в проект
Для создания проекта нам понадобится Xcode 8 (в данный момент доступна beta-версия). Создаем новый проект знакомым нам способом, далее необходимо создать новый target для расширения через меню File — New — Target и выбрать Notification Content Extension.
Для расширения создается свой storyboard и view controller, который подчиняется протоколу UNNotificationContentExtension.
В нём присутствует один обязательный метод
func didReceive(_ notification: UNNotification)
который вызывается при получении нового уведомления в тот момент, когда расширение отображается на экране. В этом методе мы можем достать нужную информацию из уведомления и отобразить её. В рассматриваемом примере для простоты мы не будем его реализовывать.
Далее мы будем использовать другой необязательный метод
func didReceive(_ response: UNNotificationResponse, completionHandler completion: (UNNotificationContentExtensionResponseOption) -> Swift.Void)
чтобы обрабатывать нажатия кнопок (Notification Actions) и производить изменения в интерфейсе, открывать главное приложение.
Проектирование и стилизация интерфейса
Затем верстаем интерфейс как душе угодно в storyboard-файле, конфигурируем в унаследованных методах
UIViewController
.Если мы не хотим, чтобы заголовок, подзаголовок, текст уведомления показывались в экране расширения, нужно указать ключ UNNotificationExtensionDefaultContentHidden
со значением YES
в plist.
При отображении экрана с расширением можно заметить проблему с его высотой. К сожалению, здесь нам недоступен механизм автоматического подсчета высоты. Исправить это можно следующим кодом, например, в методе viewDidLoad
, предполагая высоту в 50%, достаточную для отображения всего контента:
let size = view.bounds.size
preferredContentSize = CGSize(width: size.width, height: size.width / 2)
В этом случае могут наблюдаться артефакты при показе — пользователь может заметить анимацию изменения размера высоты экрана.
К счастью, это можно исправить установкой коэффициента от максимальной высоты в plist (ключ UNNotificationExtensionInitialContentSizeRatio
).
Окончательно plist выглядит следующим образом:
Добавление действий и их обработка
Для того, чтобы настроить пользовательские действия с уведомлением (Notification Actions), нужно их заранее подготовить — создать категорию уведомления и присвоить ей возможные действия над уведомлением:
let showMoreAction = UNNotificationAction(identifier: "showMore", title: "Подробнее", options: [])
let addBalanceAction = UNNotificationAction(identifier: "addBalance", title: "Пополнить на 500 ₽", options: [])
let myPlanAction = UNNotificationAction(identifier: "myPlan", title: "Мой тариф", options: [])
let balanceCategory = UNNotificationCategory(identifier: "balance", actions: [showMoreAction, addBalanceAction, myPlanAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([balanceCategory])
Категория нужна для того, чтобы операционная система по её идентификатору показала возможные действия. Категория должна приходить в соответствующем поле в пуше:
{
aps : {
alert : "Текст пуша",
category: "balance"
}
}
Для того, чтобы эти действия можно было применить к локальному уведомлению, нужно заполнить соответствующее поле класса UNNotificationContent
:
content.categoryIdentifier = "balance"
Дополнительно для показа конкретного расширения (если их несколько) нужно добавить в массив с ключом UNNotificationExtensionCategory
идентификатор категории в plist. Если операционная система не находит ключ категории в доступных расширениях, уведомление обрабатывается стандартно (без показа какого-либо экрана расширения).
Нажатия на кнопки обрабатываются так:
switch response.actionIdentifier {
case "addBalance":
addBalance()
completion(.doNotDismiss)
case "myPlan":
openMainApplication()
completion(.dismiss)
case "showMore":
openMainApplication()
completion(.dismiss)
default:
completion(.dismiss)
}
В этом методе мы можем выполнять любые (в том числе асинхронные) операции, но время на обработку действия ограничено, поэтому не стоит перегружать метод сложными и длительными процессами.
Completion должен быть вызван с элементом перечисления UNNotificationContentExtensionResponseOption
:
- dismiss — после выполнения уведомление скроется
- doNotDismiss — после выполнения уведомление не скроется
- dismissAndForwardAction — после выполнения уведомление скроется и действие перенаправится в основное приложение в метод
func userNotificationCenter(_ center:, didReceive response:, withCompletionHandler completionHandler: )
делегатаUNUserNotificationCenterDelegate
В нашем тестовом приложении мы асинхронно пополняем счет:
По другим действиям осуществляем переход в основное приложение привычным для расширения способом (предварительно зарегистрировав URL-схему приложения):
if let url = URL(string: "callProvider://") {
extensionContext?.open(url, completionHandler: nil)
}
Ограничения по кастомизации интерфейса
В заключение перечислю ограничения по кастомизации расширения (этим знанием стоит поделиться с дизайнерами):
- Стили кнопок действий над уведомлением не кастомизируются. Нельзя поменять шрифт, выравнивание, добавить иконки к кнопкам и т.д.
- Фон экрана расширения не поддерживает (надеюсь, это касается только beta-версии) прозрачность. Все попытки установить фон в прозрачный и получить стандартный blur на фоне (как это работает в виджете), были неудачны.
- Белый заголовок экрана расширения (с названием приложения и иконкой «закрыть») также полностью неконфигурируемый.
Подробнее об уведомлениях — в лекциях с WWDC 2016: Introduction to Notifications и Advanced Notifications.
Благодарности за дизайн: Степанов Никита, Отрощенко Денис.