iOS 10: Notification Content Extension

c511b5fa4da1433abe13baaeeb95f6cb.png

В этой статье речь пойдет о новой возможности в 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.

d4b094d5711b44bba64528123987c7fd.png

Для расширения создается свой storyboard и view controller, который подчиняется протоколу UNNotificationContentExtension.

В нём присутствует один обязательный метод

func didReceive(_ notification: UNNotification)

который вызывается при получении нового уведомления в тот момент, когда расширение отображается на экране. В этом методе мы можем достать нужную информацию из уведомления и отобразить её. В рассматриваемом примере для простоты мы не будем его реализовывать.

Далее мы будем использовать другой необязательный метод

func didReceive(_ response: UNNotificationResponse, completionHandler completion: (UNNotificationContentExtensionResponseOption) -> Swift.Void)

чтобы обрабатывать нажатия кнопок (Notification Actions) и производить изменения в интерфейсе, открывать главное приложение.

Проектирование и стилизация интерфейса


Затем верстаем интерфейс как душе угодно в storyboard-файле, конфигурируем в унаследованных методах UIViewController.
2a25908e5ea84beba7a1a68d7dbc48ce.png

Если мы не хотим, чтобы заголовок, подзаголовок, текст уведомления показывались в экране расширения, нужно указать ключ UNNotificationExtensionDefaultContentHidden со значением YES в plist.

При отображении экрана с расширением можно заметить проблему с его высотой. К сожалению, здесь нам недоступен механизм автоматического подсчета высоты. Исправить это можно следующим кодом, например, в методе viewDidLoad, предполагая высоту в 50%, достаточную для отображения всего контента:

let size = view.bounds.size
preferredContentSize = CGSize(width: size.width, height: size.width / 2)

В этом случае могут наблюдаться артефакты при показе — пользователь может заметить анимацию изменения размера высоты экрана.

ba5ad1626d8b4ff98ea303beac7ee9c6.gif

К счастью, это можно исправить установкой коэффициента от максимальной высоты в plist (ключ UNNotificationExtensionInitialContentSizeRatio).

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

5fff981b5b8848d498abcf1d22b7b6ba.png

Добавление действий и их обработка


Для того, чтобы настроить пользовательские действия с уведомлением (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

В нашем тестовом приложении мы асинхронно пополняем счет:

ca06865b9ce849c98642ec944923a135.gif

По другим действиям осуществляем переход в основное приложение привычным для расширения способом (предварительно зарегистрировав URL-схему приложения):

if let url = URL(string: "callProvider://") {
    extensionContext?.open(url, completionHandler: nil)
}

Ограничения по кастомизации интерфейса

В заключение перечислю ограничения по кастомизации расширения (этим знанием стоит поделиться с дизайнерами):

  1. Стили кнопок действий над уведомлением не кастомизируются. Нельзя поменять шрифт, выравнивание, добавить иконки к кнопкам и т.д.
  2. Фон экрана расширения не поддерживает (надеюсь, это касается только beta-версии) прозрачность. Все попытки установить фон в прозрачный и получить стандартный blur на фоне (как это работает в виджете), были неудачны.
  3. Белый заголовок экрана расширения (с названием приложения и иконкой «закрыть») также полностью неконфигурируемый.

Подробнее об уведомлениях — в лекциях с WWDC 2016: Introduction to Notifications и Advanced Notifications.

Благодарности за дизайн: Степанов Никита, Отрощенко Денис.

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

© Habrahabr.ru