it-roy-ru.com

Метод обратного вызова, если пользователь отклоняет Push Notification Prompt?

Моя проблема в том, что я хочу показать загрузочный экран для начальной подсказки push-уведомлений «Приложение хочет отправлять вам push-уведомления».

Поэтому, если пользователь нажимает yes, я могу продолжить и запустить приложение в затем вызванных методах делегата:

- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
  [self hideLoadingScreen];
}

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
  [self hideLoadingScreen];
}

Однако если пользователь нажимает no, ни один из этих методов не вызывается, что имеет смысл. У меня вопрос, есть ли другой метод делегата, который срабатывает, если он отказывается?

Моя проблема в том, что если выбран no, экраны загрузки никогда не исчезают. Итак, мне как-то нужно знать, когда пользователь завершит выбор.

33
最白目

В iOS 7, когда появляется системный запрос push-уведомлений, приложение становится неактивным, и запускается UIApplicationWillResignActiveNotification. Аналогичным образом, когда пользователь отвечает на запрос (нажимая кнопку «Да» или «Нет»), приложение снова становится активным, и запускается UIApplicationDidBecomeActiveNotification.

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

Примечание. Во время отображения подсказки кнопка «Домой», Центр уведомлений и Центр управления отключены, поэтому они не могут вызвать ложноположительное UIApplicationDidBecomeActiveNotification. Однако, если пользователь нажимает кнопку блокировки, он запускает UIApplicationDidBecomeActiveNotification.

31
Jeff Mascia

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

UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];

Имейте в виду, пользователь также может отключить уведомления в настройках телефона.

Если вы проверите это на didRegisterForRemoteNotificationsWithDeviceToken, вы должны увидеть, включены ли типы, которые вы запрашивали.

6
Grzegorz Krukowski

Вот как я это сделал в Swift 3. Они имеют ключевое значение для внутреннего отслеживания состояния жизненного цикла приложения. Когда представлена ​​Push-подсказка, приложение подает в отставку, активно, но не входит в фон. Это все в моем AppDelegate.Swift.

Это действительно большой взломать и не рекомендуется в производстве. Apple может изменить способ представления этих предупреждений, и это может прерваться в любое время. Это было проверено с использованием различных iPhone и iPad под iOS 9 и 10.

/// An internal value used to track application lifecycle state
enum ApplicationLifecycleState {
    case willResignActive
    case didEnterBackground
    case willEnterForeground
    case didBecomeActive
    case unknown
}

/// This is used purely for tracking the application lifecycle for handling the system Push notification alert
var internalLifecycleState: ApplicationLifecycleState = .unknown {
    didSet {
        // If we're not in the middle of asking for Push permissions, none of the below applies, just bail out here
        if !isAskingForPushPermissions { return }

        // WARNING: Application lifecycle trickery ahead
        // The normal application lifecycle calls for backgrounding are as follows:
        // applicationWillResignActive -> applicationDidEnterBackground -> applicationWillEnterForeground -> applicationDidBecomeActive
        // However, when the system Push notification alert is presented, the application resigns active, but does not enter the background:
        // applicationWillResignActive -> [user taps on alert] -> applicationDidBecomeActive
        // We can use this discrepancy to our advantage to detect if the user did not allow Push permissions

        // If applicationDidBecomeActive
        // AND the previous state was applicationWillResignActive
        // AND the notification types bitmask is 0, we know that the user did not allow Push permissions
        // User denied permissions
        if internalLifecycleState == .didBecomeActive
            && oldValue == .willResignActive
            && UIApplication.shared.currentUserNotificationSettings?.types.rawValue == 0 {
            // We're done
            firePushCompletionBlockAndCleanup(registered: false)
        } else {
            // The state below can only be entered on iOS 10 devices.
            // If the user backgrounds the app while the system alert is being shown,
            // when the app is foregrounded the alert will dismiss itself without user interaction.
            // This is the equivalent of the user denying Push permissions.
            // On iOS versions below 10, the user cannot background the app while a system alert is being shown.

            if #available(iOS 10, *), internalLifecycleState == .didBecomeActive {
                firePushCompletionBlockAndCleanup(registered: false)
            }
        }
    }
}

/// Used internally to track if the system Push notification alert is currently being presented
var isAskingForPushPermissions = false

typealias PushNotificationRegistrationCompletionBlock = ((_ registered: Bool) -> Void)

// ...

func applicationWillResignActive(_ application: UIApplication) {    
    internalLifecycleState = .willResignActive
}

func applicationDidEnterBackground(_ application: UIApplication) {
    internalLifecycleState = .didEnterBackground
}

func applicationWillEnterForeground(_ application: UIApplication) {
    internalLifecycleState = .willEnterForeground
}

func applicationDidBecomeActive(_ application: UIApplication) {
    internalLifecycleState = .didBecomeActive
}

// ...

func setupPushNotifications(_ application: UIApplication = UIApplication.shared, completion: @escaping PushNotificationRegistrationCompletionBlock) {
    isAskingForPushPermissions = true
    pushCompletionBlock = completion
    let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
    application.registerUserNotificationSettings(settings)
    application.registerForRemoteNotifications()
}

fileprivate func firePushCompletionBlockAndCleanup(registered: Bool) {
    pushCompletionBlock?(registered)
    pushCompletionBlock = nil
    isAskingForPushPermissions = false
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

    // application:didRegisterForRemoteNotificationsWithDeviceToken may be called more than once (once for each notification type)
    // By checking that the notification types bitmask is greater than 0, we can find the final time this is called (after the user actually tapped "allow")
    // If the user denied Push permissions, this function is never called with a positive notification type bitmask value
    if UIApplication.shared.currentUserNotificationSettings?.types.rawValue ?? 0 > 0 {
        firePushCompletionBlockAndCleanup(registered: true)
    }
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("Failed to register for notifications with error: " + error.localizedDescription)
    firePushCompletionBlockAndCleanup(registered: false)
}

Использование:

appDelegate.setupPushNotifications(completion: { [weak self] (registered) in
    // If registered is false, the user denied permissions
})
2
JAL

Для Swift 3 и Swift 4.0 Использование NotificationCenter и метода AppDelegate didRegister notificationSettings. Параметры NotificationSettings показывают, выбрали ли пользователи значки, звуки и т.д., И будут ли они пустым массивом, если они отклонят push-уведомления. Он запускается специально, когда пользователи отвечают на запрос push-уведомлений, и, похоже, это то, что используется большинством разработчиков, поскольку он более специфичен, чем проверка didBecomeActive. Но Apple может изменить это. Кто знает?

К сожалению, NotificationCenter не имеет предварительно заданного имени уведомления, поэтому вам нужно либо настроить и расширить (см. Конец), либо использовать необработанное значение (в SO больше об этом).

В AppDelegate:

    func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
      // if not registered users will have an empty set of settings
      let accepted: Bool = !notificationSettings.types.isEmpty
      NotificationCenter.default.post(name: Notification.Name(rawValue: "didRespondToPrompt"), object: self, userInfo: ["didAccept" : accepted])
}

Затем наблюдайте, куда вам нужно, например, в контроллере вида:

class MyViewController: UIViewController {

//MARK: - Lifecycle
   override func viewDidLoad() {
      super.viewDidLoad()
      NotificationCenter.default.addObserver(self, selector: #selector(MyViewController.didRespondToPushPrompt(_:)), name: NSNotification.Name(rawValue: "didRespondToPrompt"), object: nil)

   }
    @objc func didRespondToPushPrompt(_ notification: Notification) {

       if let userInfo: [AnyHashable : Any] = notification.userInfo, let didAccept: Bool = userInfo[NSNotificationKeyNames.didAccept] as? Bool, !didAccept {
        //if user doesn't accept, do this...

       } else  {
       //all other situations code goes here
      }

   }
}

Пара вещей: во-первых, для Swift 4.0 я использую "@objc" перед одним методом, но это не обязательно для Swift 3.
Кроме того, для использования NotificationCenter на практике я не использовал «rawValue». Вместо этого я сделал расширение так: 

import Foundation

extension NSNotification.Name {
   static let DidRegisterForPushNotifications = NSNotification.Name("DidRegisterForPushNotifications")
}

Который я мог бы затем использовать так:

NotificationCenter.default.post(name: Notification.Name.DidRegisterForPushNotifications, object: self, userInfo: ["didAccept" : myBool]) и т. д. и т. д.

1
davidrynn

Не могли бы вы просто сделать следующее:

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
    BOOL pushEnabled = notificationSettings.types & UIUserNotificationTypeAlert;
}

Этот метод должен быть обратным вызовом для этого Push-уведомления, и оттуда вы можете проверить битовую маску, чтобы увидеть, были ли Push-уведомления включены или нет.

1
NYC Tech Engineer

Некоторые из ответов здесь уже не актуальны или более сложны, чем следовало бы, поскольку в среде UserNotifications и iOS 10 вы можете легко получить эти данные примерно так:

let center = UNUserNotificationCenter.current()

// Request permission to display alerts and play sounds.
center.requestAuthorization(options: [.alert, .sound]) 
{ (granted, error) in
  // Enable or disable features based on authorization.
}
1
Nermin Sehic

Я полагаю, у вас может быть переменная BOOL, чтобы проверить ее в AppDelegate, потому что, похоже, нет другого выхода, кроме как использовать внешние API. Смотрите это .

AppDelegate.m

// declare a BOOL 
BOOL allow = NO;

- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
allow = YES;
  [self hideLoadingScreen];
}

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
  allow = YES;
  [self hiedLoadingScreen];
}

Теперь, я думаю, вы можете получить доступ к этой переменной BOOL, чтобы различать, когда нажимать «Не разрешать» или нет.

0
Puneet Sharma

Вот пример кода Swift 2 для вас, ребята ... Это немного сложно, но я надеюсь, что мои комментарии помогут вам понять это.

Определить переменные

var appDidBecomeActiveCount = 0
var userDefaults:NSUserDefaults!

AppDelegate - didFinishLaunchingWithOptions

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

        userDefaults = NSUserDefaults.standardUserDefaults()
        if userDefaults.valueForKey("FirstLaunche") == nil {
            userDefaults.setBool(true, forKey: "FirstLaunche")
            userDefaults.synchronize()
        }

        // Register for notification
        //iOS 8+
        let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: [UIUserNotificationType.Alert , UIUserNotificationType.Badge ,UIUserNotificationType.Sound], categories: nil)
        UIApplication.sharedApplication().registerUserNotificationSettings(settings)
        UIApplication.sharedApplication().registerForRemoteNotifications()
}

AppDelegate - applicationDidBecomeActive

func applicationDidBecomeActive(application: UIApplication) {
            //Delay until alert get dismissed and notification type setted in app
            delay(0.5, closure: { () -> () in
                self.checkTheDilemma()
            })
}
//I love this short method <3_<3
func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

Проверить действие

func checkTheDilemma (){
        //Checking if this user turned off Push notifications or didn't allow it at all
        let notificationType = UIApplication.sharedApplication().currentUserNotificationSettings()?.types

        if userDefaults.valueForKey("FirstLaunche") as! Bool == true {
            //User now is asked for notification permission because it's app's first launche
            // if appDidBecomeActiveCount == 0 --> Pop up message will appeare
            // if appDidBecomeActiveCount == 1 --> Pop up message dismissed
            // if notificationType?.rawValue == 0 --> Notifications off
            // if notificationType?.rawValue > 0  --> Notifications on
            if notificationType?.rawValue == 0
                && appDidBecomeActiveCount == 1 { //If user disabled notifications from pop up alert
                    // ** User just tapped "Don't allow" btn :\
                    // Do what ever you are here for

                    //Now set FirstLaunche = false
                    userDefaults.setBool(false, forKey: "FirstLaunche")
                    userDefaults.synchronize()
            }
        } else {
            if notificationType?.rawValue == 0
                && appDidBecomeActiveCount == 0 { // This guy is not registered for Push notification
                    // ** User disabled notifications in past (because this is not his first launch)
            }
        }
        appDidBecomeActiveCount++
    }
0
Husam

Вы можете определить, отменил ли пользователь запрос на уведомление в методе didRegisterUserNotificationSettings, который срабатывает после вызова registerForRemoteNotificationTypes, проверив код notificationSettings.types.

Если вы запросили несколько настроек, но notificationSettings.types == UIUserNotificationTypeNone означает, что пользователь отменил запрос. 

Но не забывайте, что метод registerForRemoteNotificationTypes устарел!

0
Finskii