iOS 12: новинки в уведомлениях

Конференция WWDC прошла, а докладов, которые стоит посмотреть, осталось ещё очень много. Были ключевые темы, которым Apple уделила особое внимание. Core ML, Siri Shortcuts и, конечно же, изменения в Notifications.


tnoh1klclj31_juce5ku2drd9x8.png

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

Начнём с обзора возможностей, которые добавила Apple.


Группировка уведомлений

Для реализации ничего делать не нужно. iOS 12 автоматически сгруппирует сообщения за вас. Но есть нюансы, которые касаются кастомизации, локализации и группировки, но не на основе идентификатора приложения, а, например, в зависимости от имени пользователя, который отправил уведомление.

Кроме того, если вы будете тестировать группировку нотификаций, обратите внимание — они начнут собираться в пачку только в том случае, если единовременно все нотификации не могут поместиться на экране. Например, на экране iPhone 8 для этого нужно разместить 5 и более нотификаций.


ktbild9jk0ey2gbbdbr4rhcvdow.png

Чтобы не перегружать этот материал, я вынес его в отдельный текст, который появится на Хабре в ближайшее время.


Изменения в API NSExtensionContext

Следующий пункт — новые возможности для нотификаций и в частности класса NSExtensionContext. Он отвечает за взаимодействие с виджетами, Siri, проигрывание медиаконтента. Нас больше интересуют уведомления. Были добавлены два метода и одна переменная:

var notificationActions: [UNNotificationAction] { get set }

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

func dismissNotificationContentExtension()
func performNotificationDefaultAction()

Методы открывают приложение, либо скрывают уведомление.

Для демонстрации возможностей напишем небольшое приложение.

Сперва добавим в приложение отправку локальных уведомлений:

let actions = [
    UNNotificationAction(identifier: "like-action",  title: "Like", options: []),
    UNNotificationAction(identifier: "open-app",  title: "Open App", options: []),
    UNNotificationAction(identifier: "dismiss",  title: "Dismiss", options: []),
]

let simpleCategory = UNNotificationCategory(identifier: "category-simple", actions: actions, intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([simpleCategory])


  • Создаём несколько действий. Идентификатор позволит различать их при обработке, заголовок кнопки и опции. Например выявит то, что для выполнения действия требуется аутентификация пользователя.
  • Определяем категорию с идентификатором. Идентификатор категории позволяет обрабатывать и отображать различные типы уведомлений по-разному.
  • Последним шагом устанавливаем категорию для центра нотификаций.

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

UNUserNotificationCenter.current().getNotificationSettings {
    (settings) in

    guard settings.authorizationStatus == .authorized else { return }

    let content = UNMutableNotificationContent()
    content.title = "Cat Title"
    content.subtitle = "Cat Subtitle"
    content.body = "Cat Body"
    content.sound = .default
    content.categoryIdentifier = "category-simple"

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
    let uuid = UUID().uuidString
    let request = UNNotificationRequest(identifier: uuid, content: content, trigger: trigger)

    UNUserNotificationCenter.current().add(request, withCompletionHandler: {
        (error) in

    })
}


  • Проверяем, позволил ли нам пользователь отправлять уведомления.
  • Создаём уведомление с заголовком и текстом и указываем категорию, к которой оно будет относится. В данном случае — «category-simple».
  • Устанавливаем триггер срабатывания через 3 секунды.
  • Заполняем запрос на отправку уведомления. В данном случае в качестве идентификатора используем UUID. Этот идентификатор может понадобиться, если мы захотим отменить запланированное уведомление.
  • Добавляем наш запрос в центр нотификаций.

Далее необходимо добавить в приложение новый таргет Notification Content Extension. Он позволяет настроить параметры отображения уведомлений и обработку действий.


y3r1jvhoccua49gmfis7bt0gxzu.png

Будет создан plist-файл, ViewController и Storyboard:


wxluxrgxvmap9s-enh1-fm6c9y4.png

В plist-файле нас интересуют следующие ключи:


  • UNNotificationExtensionCategory — название категории, которая будет обрабатываться. Как и ранее, укажем «category-simple».
  • UNNotificationExtensionInitialContentSizeRatio — соотношение высоты уведомления к его ширине. Повлияет на размер уведомления после отображения в полном виде.
  • UNNotificationExtensionUserInteractionEnabled — включаем или отключаем взаимодействие с кастомными контролами. В нашем случае это будет кнопка с сердцем.
  • UNNotificationExtensionDefaultContentHidden — скрывает содержимое нотификации, которое формируется по умолчанию.

В сториборд создаём UIImageView, UILabel для отображения заголовка уведомления и UIButton для взаимодействия с приложением.


gb2vkrmvi_vrpjajntm6eohazkc.png

Во View Controller’е создаём методы для открытия приложения и скрытия нотификации:

func openApp() {
    extensionContext?.performNotificationDefaultAction()
}

func dismissNotification() {
    extensionContext?.dismissNotificationContentExtension()
}

Реализуем методы протокола UNNotificationContentExtension.

Первый позволит отобразить необходимый текст:

func didReceive(_ notification: UNNotification) {
    self.notificationTitleLabel.text = notification.request.content.body
}

Второй нужен для обработки действий от UNNotificationAction. В этом же методе выполняется подмена действий с помощью присвоения extensionContext?.notificationActions:

func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
    switch response.actionIdentifier {
    case "like-action":
        let actions = [
            UNNotificationAction(identifier: "1-star",  title: "★", options: []),
            UNNotificationAction(identifier: "2-star",  title: "★ ★", options: []),
            UNNotificationAction(identifier: "3-star",  title: "★ ★ ★", options: []),
            ]
        extensionContext?.notificationActions = actions
    case "open-app":
        openApp()
    default:
        dismissNotification()
    }
}

Обработка нажатий на сердце выполняется как обычно, через IBAction:

@IBAction func defaultButtonTapped(_ sender: UIButton) {
    openApp()
}


xkklip58-bi_uwtym3svgvgqnkm.gif

Запускаем приложение и смотрим, что у нас получилось:


  • Нажатия от UIButton обрабатываются.
  • Использование UNNotificationAction позволяет заменить доступные для взаимодействия варианты.


Взаимодействие с настройками уведомлений

Следующее нововведение позволяет добавить в настройки уведомлений новый пункт меню. При тапе на него будет осуществлён вызов метода, который вы можете реализовать в приложении. Например, пользователь может напрямую из системных настроек попасть в ваше приложение и в нём включить только те нотификации, которые он действительно хочет получать. Что нужно сделать для реализации?

Во-первых, при авторизации нотификаций добавляем ещё один параметр — providesAppNotificationSettings:

UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound, .providesAppNotificationSettings])

Во-вторых, реализуем метод userNotificationCenter (_: openSettingsFor:) протокола UNUserNotificationCenterDelegate:

extension AppDelegate: UNUserNotificationCenterDelegate {

    func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
        openSettings()
    }

    func openSettings() {
        let storyboard = UIStoryboard(name: "Settings", bundle: nil)
        let settings = storyboard.instantiateViewController(withIdentifier: "Settings")
        window?.rootViewController = settings
    }

}


pstogmnrnxt-qssgqokkysy7ljk.png


Provisional Notifications

Пользователь не всегда понимает, хочет ли он получать уведомления от вашего приложения. Поэтому при первом запуске приложения такой выбор ему сделать сложно. С большой вероятностью он откажется от вашего предложения. Для таких ситуаций Apple предлагает использовать Provisional Authorization. При запросе авторизации на отправку нотификаций добавляется ещё один параметр — provisional. authorizationStatus для таких уведомлений, также приходит в приложение со статусом provisional.

UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .provisional])

Пользователь при запуске приложения не получит запрос на авторизацию. Однако, чтобы его не беспокоить, приложение помещается в так называемый jail. Для уведомлений отключены звуки, бейджи; они отображаются только в Notification Center. На заблокированном экране или в виде баннеров они появляться не будут.

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


mgwqcnuqeahzyuuv3e7eqrbshpo.png


Critical Alerts

Последнее изменение добавляет ещё один тип уведомлений — Critical Alerts. Эти уведомления полностью игнорируют настройки вашего телефона, выключенный звук или режим «не беспокоить». Apple рекомендует использовать их в медицинских приложениях (например, у пользователя устройства резко подскочил уровень сахара), а также для обеспечения безопасности пользователей дома или в публичных местах.

Запрос на авторизацию содержит особый знак:


cge6ugnjpp9kmzr11zq_gck4tt4.png

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


hifcozpqldx_qkybllpj_mwuyvm.png

Для отправки критических уведомлений придётся пройти процедуру валидации вашего приложения на сайте Apple.

Для использования уведомлений применяем параметр criticalAlert:

UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .criticalAlert])

И формируем содержимое нотификации:

let content = UNMutableNotificationContent()
content.title = "WARNING"
content.body = "Storm alert"
content.categoryIdentifier = "storm-alert"
content.sound = UNNotificationSound.defaultCriticalSound(withAudioVolume: 1.0)

Для Critical Alerts можно указать громкость, с которой нотификация будет срабатывать независимо от настроек пользователя.

Надеюсь, этот материал упростит процесс внедрения новых уведомлений в ваше приложение. Если с какими-то этапами реализации не получилось разобраться, предлагаю ознакомиться с кодом на github или задать вопрос в комментах.

Также можете посмотреть статью от e-Legion по уведомлениям для iOS 10 или запись доклада с WWDC — What«s New in User Notifications. Обсудить нововведения сможем на MBLT DEV 2018 в Москве 28 сентября.

Хорошего дня и котиков всем ^_^

© Habrahabr.ru