Конференция 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 сентября.
Хорошего дня и котиков всем ^_^
frozen_daniil
О да, темная тема в MacOS!