问题
My problem is I want to show a loading screen for the initial Push Notification Prompt \"The app wants to send you push notifications.\"
So if the user hits yes
I can proceed and start the app in the then invoked delegate methods:
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
[self hideLoadingScreen];
}
However if the user hits no
, none of these methods get called, which makes sense. My question is, is there a different delegate method that gets fired if he declines?
My problem is if no
is selected, the loading screens never disappear. So I somehow need to know when the user is done with the selection.
回答1:
In iOS 7, when the system's push notification prompt appears, the app becomes inactive and UIApplicationWillResignActiveNotification fires. Similarly when the user responds to the prompt (pressing either Yes or No), the app becomes active again and UIApplicationDidBecomeActiveNotification fires.
So you can listen for this notification, and then hide your loading screen.
Note: While the prompt is displayed, the Home button, Notification Center, and Control Center are disabled so they cannot trigger a false-positive UIApplicationDidBecomeActiveNotification. However if the user presses Lock button it will trigger UIApplicationDidBecomeActiveNotification.
回答2:
You can always get current allowed notification types from:
UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
Keep in mind user can also disable notification in phone settings.
If you check that on didRegisterForRemoteNotificationsWithDeviceToken you should see if types you asked for are enabled.
回答3:
Couldn't you just do the following:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
BOOL pushEnabled = notificationSettings.types & UIUserNotificationTypeAlert;
}
This method should be the callback to that push notifications prompt, and from there, you can check the bitmask to see if push notifications were enabled or not.
回答4:
Here's how I did it in Swift 3. They key here is to keep track of the application's lifecycle state internally. When the push prompt is presented, the application resigns active, but does not enter the background. This is all in my AppDelegate.swift.
This is a really big hack and is not recommended in production. Apple could change the way these alerts are presented and this could break at any time. This was tested using various iPhones and iPads running iOS 9 and 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)
}
Usage:
appDelegate.setupPushNotifications(completion: { [weak self] (registered) in
// If registered is false, the user denied permissions
})
回答5:
Some of the answers here are not relevant anymore, or are more complicated than it should be, since UserNotifications framework and iOS 10 you can easily get this data like so:
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.
}
回答6:
For Swift 3 and Swift 4.0
Using NotificationCenter and the AppDelegate method didRegister notificationSettings
. NotificationSettings show whether the users opted for badges, sounds, etc. and will be an empty array if they declined push notifications. It is fired specifically when users respond to the push notifications prompt and seems to be what most devs use, since it's more specific than checking didBecomeActive. But Apple might change this. Who knows?
Unfortunately, NotificationCenter does not have a preset notification name so you either have to setup and extension (see end) or use the raw value in (SO has more on this).
In 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])
}
Then observe wherever you need to, for example in a 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
}
}
}
Couple of things: First, for Swift 4.0, I'm using "@objc" in front of one method, but it's not necessary for Swift 3.
Also, for using NotificationCenter, in practice I did not use "rawValue". Instead I made an extension like so:
import Foundation
extension NSNotification.Name {
static let DidRegisterForPushNotifications = NSNotification.Name("DidRegisterForPushNotifications")
}
Which I could then use like so:
NotificationCenter.default.post(name: Notification.Name.DidRegisterForPushNotifications, object: self, userInfo: ["didAccept" : myBool])
etc., etc.
回答7:
I guess you can have a BOOL variable to check it in your AppDelegate because there seems to be no way other than using external APIs. See 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];
}
Now I guess you can access this BOOL variable to differentiate when Don't allow is pressed or not.
回答8:
Here is a SWIFT 2 code example for you guys ... It's complicated little bit ,but I hope my comments will help you understand it.
Define variables
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)
}
Check action
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++
}
回答9:
You can detect if user has cancelled the notification prompt in didRegisterUserNotificationSettings
method that fires after calling registerForRemoteNotificationTypes
by checking the notificationSettings.types
.
If you have requested a number of settings but notificationSettings.types == UIUserNotificationTypeNone
means, that user has cancelled the prompt.
But don't forget that registerForRemoteNotificationTypes
method is now deprecated!
回答10:
2nd May 2019
This is the implementation to check if notifications are authorized any time in your app, Simple call this function.
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.")
}
}
}
来源:https://stackoverflow.com/questions/19053092/callback-method-if-user-declines-push-notification-prompt