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

    Конференция 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 сентября.


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


    UPD: Вторая часть статьи про группировку уведомлений уже на Хабре.

    e-Legion 116,11
    Лидер мобильной разработки в России
    Поделиться публикацией
    Похожие публикации
    Комментарии 19
      +1
      О да, темная тема в MacOS!
        +1
        Очень удобно и комфортно для глаз, особенно, когда освещение приглушено. Уже и не хочется на High Sierra возвращаться.
          0
          А она работает совместно с Night Shift или вместо Night Shift?
            +1
            Night Shift можно совместно с тёмной темой использовать. Это независимые настройки.
        +2
        А где уши у кота?
          0
          Котик их спрятал ^_^
            0
            Это ваш котэ?
              0
              Нет, просто из интернета картинка.
          +3
          Статья просто прекрасна,
          Спасибо
            0
            Большое спасибо!
            +2

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

              0
              Очень приятно. А на чём пишите или не имеете вообще отношения к разработке приложений?
                +1

                C#, delphi, php, сейчас вот kotlin штудирую

                  +1
                  Круто! На C# писал небольшие программы (в том числе используя Xamarin для разработки под iOS). А код на Kotlin очень легко воспринимается, если знаком со Scala или Swift.
              0
              Главное, что бы маркетологи не узнали про Critical Alerts
                0
                Там очень сложная система для валидации, так что вряд ли этим смогут злоупотреблять.
                0
                Critical Alerts даже с локальными уведомлениями на тестовом приложении не получится воспроизвести без валидации? У меня не получись воспроизвести. Даже при запросе авторизации не выходит соответствующее предупреждение
                  0
                  Насколько я понял Critical Alerts без подтверждения со стороны Apple даже в тестовом приложении не будут работать. Попробую написать в поддержку Apple, чтобы узнать подробнее.
                    0
                    Если что то получится узнать, дайте знать пожалуйста! Статья очень понравилась, спасибо!

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

                Самое читаемое