web-dev-qa-db-ja.com

ユーザーがプッシュ通知プロンプトを拒否した場合のコールバックメソッド?

私の問題は、最初のプッシュ通知プロンプト「アプリがプッシュ通知を送信したい」のロード画面を表示したいことです。

そのため、ユーザーがyesを押すと、次に呼び出されたデリゲートメソッドでアプリを続行して起動できます。

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

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

ただし、ユーザーがnoをヒットした場合、これらのメソッドはいずれも呼び出されないため、意味があります。私の質問は、彼が辞退した場合に起動される別のデリゲートメソッドがありますか?

私の問題は、noが選択されている場合、ロード画面が消えることはありません。だから、どういうわけかユーザーが選択を完了したことを知る必要があります。

35
最白目

IOS 7では、システムのプッシュ通知プロンプトが表示されると、アプリが非アクティブになり、UIApplicationWillResignActiveNotificationが起動します。同様に、ユーザーがプロンプトに応答すると([はい]または[いいえ]を押す)、アプリが再びアクティブになり、UIApplicationDidBecomeActiveNotificationが起動します。

そのため、この通知を聞いて、ロード画面を非表示にすることができます。

注:プロンプトが表示されている間、ホームボタン、通知センター、およびコントロールセンターは無効になっているため、誤検知のUIApplicationDidBecomeActiveNotificationをトリガーできません。ただし、ユーザーが[ロック]ボタンを押すと、UIApplicationDidBecomeActiveNotificationがトリガーされます。

32
Jeff Mascia

現在許可されている通知タイプはいつでも次から取得できます。

UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];

ユーザーは電話の設定で通知を無効にすることもできます。

DidRegisterForRemoteNotificationsWithDeviceTokenでそれを確認すると、要求したタイプが有効になっているかどうかがわかります。

6

次のことはできませんでした:

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

このメソッドは、そのプッシュ通知プロンプトのコールバックである必要があり、そこからビットマスクをチェックして、プッシュ通知が有効になっているかどうかを確認できます。

2

Swift 3での方法を次に示します。ここで重要なのは、アプリケーションのライフサイクル状態を内部的に追跡することです。プッシュプロンプトが表示されると、アプリケーションはアクティブに辞任しますが、バックグラウンドには入りません。これはすべて私のAppDelegate.Swiftにあります。

これは非常に大きなハックであり、本番環境では推奨されません。Appleはこれらのアラートの表示方法を変更し、いつでも壊れる可能性があります。これは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

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.
}
2
Nermin Sehic

SwiftおよびSwift 4.の場合NotificationCenterとAppDelegateメソッド_didRegister notificationSettings_を使用します。 NotificationSettingsは、ユーザーがバッジやサウンドなどを選択したかどうかを示し、プッシュ通知を拒否した場合は空の配列になります。ユーザーがプッシュ通知プロンプトに応答したときに特に起動され、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])
}
_

次に、たとえばView Controllerで必要な場所を観察します。

_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の場合、1つのメソッドの前で "@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

外部APIを使用する以外に方法がないように思われるので、AppDelegateでチェックするためにBOOL変数を使用できると思います。 this を参照してください。

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

2019年5月2日

これは、アプリで通知がいつでも許可されるかどうかを確認するための実装です。この関数を呼び出します。

    private func checkNotificationsAuthorizationStatus() {
    let userNotificationCenter = UNUserNotificationCenter.current()
    userNotificationCenter.getNotificationSettings { (notificationSettings) in
        switch notificationSettings.authorizationStatus {
        case .authorized:
            print("The app is authorized to schedule or receive notifications.")
        case .denied:
            print("The app isn't authorized to schedule or receive notifications.")

        case .notDetermined:
            print("The user hasn't yet made a choice about whether the app is allowed to schedule notifications.")
        case .provisional:
            print("The application is provisionally authorized to post noninterruptive user notifications.")
        }
    }

}
0
Mussa Charles