IOS 9: Как определить, когда пользователь сказал "Не разрешать" запросу push-уведомления?
В iOS 9 есть ли обратный вызов системного уровня, который я могу прочитать, который говорит мне, использовал ли пользователь "Не разрешать" на запросе push-уведомления?
Я предлагаю пользователю настраиваемый экран, информирующий их о push-уведомлениях и значении, которое он имеет в моем приложении.
![Custom Tripnary Prompt]()
У них есть два варианта: да или нет. Если они выбрали Да, я запрашиваю операционную систему для push-уведомления, и они видят всплывающее окно, как показано ниже.
![Системная подсказка системы уровня iOS для push-уведомлений]()
Теперь, если пользователь нажимает на YES, тогда есть функция с именем didRegisterForRemoteNotificationsWithDeviceToken
, которая сообщает мне, что это устройство было зарегистрировано для push-уведомлений. Я могу использовать это, чтобы перейти к следующему экрану (и перенести их на первый экран после регистрации)
Однако, как определить, что пользователь не нажал, НЕ разрешить?. Мне нужно знать, что поэтому я могу переместить пользователя на следующий экран (и перенести их на первый экран после регистрации). Функция didFailToRegisterForRemoteNotificationsWithError
не вызывается, если пользователь нажимает кнопку "Не разрешать".
Этот вопрос не является дубликатом, потому что ответ, принятый для этого вопроса, специфичен для iOS 7, где, поскольку мой вопрос специфичен, это iOS 9.
Ответы
Ответ 1
Как и в iOS 8, процесс регистрации уведомлений изменился и удалился от пользователя, которому предоставлено разрешение только на удаленные уведомления.
Теперь вы можете технически регистрироваться для удаленных уведомлений без получения разрешения от пользователя. Для вас требуется разрешение, это настройки пользовательских уведомлений (предупреждения, звуки и значки). В настоящее время они являются общими для локальных и удаленных уведомлений, что делает другие ответы технически неправильными.
Вы запрашиваете разрешение с помощью метода -[UIApplication registerUserNotificationSettings:]
на UIApplication
и в соответствии с документацией вы получаете обратный вызов для метода делегата -[UIApplicationDelegate application: didRegisterUserNotificationSettings:]
.
В заголовке есть комментарий, в котором говорится следующее:
// This callback will be made upon calling -[UIApplication registerUserNotificationSettings:]. The settings the user has granted to the application will be passed in as the second argument.
Это означает, что если пользователь не предоставил разрешения для уведомлений (как локальных, так и удаленных), то второй параметр не будет содержать никаких значений.
-[UIApplication isRegisteredForRemoteNotifications]
просто скажет вам, действительно ли приложение зарегистрировалось на Apple push-серверах и получило токен устройства:
Возвращаемое значение
ДА, если приложение зарегистрировано для удаленных уведомлений и получило токен своего устройства или НЕТ, если регистрация не была выполнена, не удалось или был отклонен пользователем.
Стоит прочитать документацию UIApplication
, поскольку она содержит всю необходимую информацию.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/#//apple_ref/occ/instm/UIApplication
Ответ 2
Мне просто удалось решить эту самую проблему, и я был бы рад поделиться тем, как я это сделал (с iOS 9.3).
В моем случае я использую одну настраиваемую кнопку для включения уведомлений с тремя возможными состояниями: по умолчанию (что означает, что пользователю еще не было предложено включить уведомления), завершено (пользователю было предложено и согласилось получить уведомления) и не удалось (пользователь отклонил приглашение уведомлений). Кнопка включена только в состоянии по умолчанию.
Теперь я не использую ни один метод здесь, а комбинацию нескольких (хотя и связанных) вызовов.
Логика следующая: даже если пользователь отклоняет приглашение уведомлений (которое появляется только один раз, пока пользователь не удалит и не переустановит приложение), мы по-прежнему регистрируем для удаленных уведомлений. Процесс будет продолжаться, как обычно, устройство будет зарегистрировано, но пользователь не получит уведомления, когда будет отправлено новое уведомление. Затем мы можем воспользоваться преимуществами как текущих настроек уведомлений, так и того, был ли пользователь уже зарегистрирован для удаленных уведомлений, чтобы знать, были ли они когда-либо запрошены (так что кнопка получает статус по умолчанию).
Этот метод не является безупречным. Если пользователь вначале соглашается получать уведомления, но позже решает вручную отключить их из настроек, тогда кнопка будет установлена в состояние по умолчанию, но после активации не будет запрашивать повторное включение уведомлений. Но в большинстве случаев это не имеет значения, так как этот тип интерфейса обычно отображается один раз во время процесса onboarding/sign up.
Что касается самого кода (Swift 2.2):
func updateButtonStatus() {
// as currentNotificationSettings() is set to return an optional, even though it always returns a valid value, we use a sane default (.None) as a fallback
let notificationSettings: UIUserNotificationSettings = UIApplication.sharedApplication().currentUserNotificationSettings() ?? UIUserNotificationSettings(forTypes: [.None], categories: nil)
if notificationSettings.types == .None {
if UIApplication.sharedApplication().isRegisteredForRemoteNotifications() {
// set button status to 'failed'
} else {
// set button status to 'default'
}
} else {
// set button status to 'completed'
}
}
Мы вызываем этот метод из нашей реализации контроллера представления viewWillAppear(animated)
.
На этом этапе должно произойти еще несколько вещей: во-первых, всякий раз, когда кнопка затрагивается (что будет происходить только в состоянии по умолчанию), мы должны предложить пользователю принять или отклонить уведомления, и мы также хотим, чтобы наши Пользовательский интерфейс должен реагировать правильно, независимо от того, какой пользователь выбирает:
@IBAction func notificationsPermissionsButtonTouched(sender: AnyObject) {
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
}
И тогда нам нужно реализовать правильные методы UIApplicationDelegate
для обработки события. Поскольку для них нет глобальных уведомлений UIApplication
, мы отправляем свои собственные:
// AppDelegate.swift
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
application.registerForRemoteNotifications()
if notificationSettings.types == .None {
NSNotificationCenter.defaultCenter().postNotificationName("ApplicationDidFailToRegisterUserNotificationSettingsNotification", object: self)
}
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
NSNotificationCenter.defaultCenter().postNotificationName("ApplicationDidRegisterForRemoteNotificationsNotification", object: self)
}
Теперь вернемся к нашему контроллеру представления, мы должны обработать эти уведомления. Итак, в наших реализациях viewWillAppear(animated)
и viewWillDisappear(animated)
мы делаем:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PermissionsViewController.applicationDidRegisterForRemoteNotificationsNotification(_:)), name: "ApplicationDidRegisterForRemoteNotificationsNotification", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PermissionsViewController.applicationDidFailToRegisterUserNotificationSettingsNotification(_:)), name: "ApplicationDidFailToRegisterUserNotificationSettingsNotification", object: nil)
updateButtonStatus()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "ApplicationDidRegisterForRemoteNotificationsNotification", object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "ApplicationDidFailToRegisterUserNotificationSettingsNotification", object: nil)
}
И сами обработчики уведомлений:
func applicationDidRegisterForRemoteNotificationsNotification(notification: NSNotification) {
let notificationSettings: UIUserNotificationSettings = UIApplication.sharedApplication().currentUserNotificationSettings() ?? UIUserNotificationSettings(forTypes: [.None], categories: nil)
if notificationSettings.types != .None {
// set button status to 'completed'
}
}
func applicationDidFailToRegisterUserNotificationSettingsNotification(notification: NSNotification) {
// set button status to 'failed'
}
Bonus
Что делать, если пользователь отклонил приглашение на уведомления, и мы хотим, чтобы у вас была кнопка, чтобы направлять их на панель настроек, где они могут снова включить ее, и заставить наш интерфейс реагировать соответствующим образом? Хорошо, я рад, что вы спросили.
Существует очень малоизвестный механизм глубокой ссылки на ваш раздел приложения внутри настроек (он был там с iOS 8, но у меня не было возможности узнать об этом до нескольких часов назад). В нашем окне кнопки touch touch мы делаем следующее:
@IBAction func settingsButtonTouched(sender: AnyObject) {
if let settingsURL = NSURL(string: UIApplicationOpenSettingsURLString) {
UIApplication.sharedApplication().openURL(settingsURL)
}
}
Поскольку мы хотим обновить наш пользовательский интерфейс, чтобы отразить любые изменения, которые пользователь мог сделать, добавим прослушиватель уведомлений для UIApplicationDidBecomeActiveNotification
в нашу реализацию viewWillAppear(animated)
(не забудьте удалить слушателя из viewWillDisapper(animated)
. наконец, изнутри соответствующего метода обработчика уведомлений мы просто вызываем наш существующий updateButtonStatus()
.
Ответ 3
В вашем делете делегат используйте этот метод
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
то вы можете узнать, предоставили ли пользователю разрешения на уведомление с помощью
[[UIApplication sharedApplication] isRegisteredForRemoteNotifications]
или используйте notificationSettings
, который вы получаете.
Ответ 4
Нет никакого способа обнаружить push-уведомление от APNS в приложении, если оно запрещено.
Используйте этот код, чтобы проверить, разрешено ли это и использовать приложение для его включения:
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"app-settings:"]];
}
Надеюсь, это поможет!
Ответ 5
Существует быстрый и дешевый способ сделать это. iOS9 этот метод делегата
- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
вызывается один раз, когда отображается диалог, а затем секунд, когда пользователь нажимает "Ok". Просто добавьте флаг здесь.
Затем, когда вы хотите отобразить пользовательское сообщение "напоминать пользователю, как включить push", просто проверьте флаг и текущие настройки уведомлений (как подробно описано в большинстве ответов выше).
- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
self.pushDialogShown = YES;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// not a great place to put this logic. For demonstration purposes ONLY
if (self.pushDialogueShown && ![self pushMessageEnabled]) {
[self showPushReminderMessage];
}
}
Ответ 6
Использование:
[[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
и NSUserDefaults
. Сохраните ключ (например, HasSeenSystemPushNotification
) до true
, когда будет представлен системный диалог для push-уведомлений.
Затем вы можете проверить ключ NSUD и параметр isRegisteredForRemoteNotifications bool, чтобы узнать, было ли оно представлено/принято и соответственно ли ваша работа.
Ответ 7
Проверьте этот метод: -
[[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
Но если ваше приложение поддерживает версию, отличную от версии iOS 8, вам необходимо выполнить проверку следующим образом: -
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
{
//notification is not enabled by user
}