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

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


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

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


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

Благодарности за дизайн: Степанов Никита, Отрощенко Денис.
Поделиться с друзьями
-->

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


  1. iWord
    09.08.2016 15:41
    +1

    Спасибо! Очень интересно.


  1. yurasoff
    09.08.2016 16:01
    +1

    Довольно удобно. Прим условии, конечно, что разработчики будут с умом использовать ее. И ограничения по кастомизации это как раз хорошо, а то будут от всех приложений сыпаться разные уведомления — одни прозрачные, другие нет, третьи цветные.
    А вообще новые эти карточки в 10ке, как по мне, смотрятся не от мира сего. Да и на локскрине не компактные они:)

    image