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



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


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


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


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


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



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


Изменения в 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. Он позволяет настроить параметры отображения уведомлений и обработку действий.



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



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


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

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



Во 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()
}


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


  • Нажатия от 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
    }

}


Provisional Notifications


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


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

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


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



Critical Alerts


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


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



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



Для отправки критических уведомлений придётся пройти процедуру валидации вашего приложения на сайте 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 сентября.


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

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


  1. frozen_daniil
    06.07.2018 12:59
    -1

    О да, темная тема в MacOS!


  1. decomeron
    06.07.2018 14:39

    А где уши у кота?


  1. tapoton
    08.07.2018 21:25
    +2

    Статья просто прекрасна,
    Спасибо


  1. darked
    09.07.2018 15:18
    +1

    Я хоть и не ios разработчик, но с удовольствием прочитал статью, спасибо