Post

iOS Notification Permission Handling|Swift Solutions for iOS 9 to iOS 12

Discover how to manage and request notification permissions effectively across iOS 9 to iOS 12 using Swift, ensuring seamless user experience and consistent app behavior.

iOS Notification Permission Handling|Swift Solutions for iOS 9 to iOS 12

点击这里查看本文章简体中文版本。

點擊這裡查看本文章正體中文版本。

This post was translated with AI assistance — let me know if anything sounds off!


Handling Push Notification Permission Status from iOS 9 to iOS 12 (Swift)

Solution for Handling Notification Permission Status and Requesting Permission on iOS 9 to iOS 12

What to do?

Continuing from the previous article “What? iOS 12 Can Send Push Notifications Without User Authorization (Swift)” about optimizing the push notification permission process, after the optimization in the previous Murmur section, a new requirement has arisen:

  1. If the user disables notifications, we can prompt them to enable it on specific feature pages.

  2. After navigating to the settings page, if the notification is turned on or off, returning to the app should reflect the updated status.

  3. If push notification permission has not been requested, ask for permission.
    If permission was requested but denied, show a prompt.
    If permission was requested and granted, proceed with the operation.

  4. Support from iOS 9 to iOS 12 is required

1 to 3 are fine. Using the UserNotifications framework introduced in iOS 10 can handle most cases properly. The tricky part is item 4, which needs to support iOS 9. For iOS 9, you have to use the old method registerUserNotificationSettings, which is not easy to handle. Let’s take it step by step!

Approach and Structure:

First, declare a global notificationStatus object to store the notification permission status. Add property observation on the pages where handling is needed (here I use Observable to subscribe to property changes; you can choose suitable KVO or use Rx, ReactiveCocoa).

Handle checking the push notification permission status and update the notificationStatus value in appDelegate methods: didFinishLaunchingWithOptions (when the app initially opens), applicationDidBecomeActive (when returning from background), and didRegisterUserNotificationSettings (for push notification permission handling in ≤iOS 9).
Pages that require handling will be triggered to perform corresponding actions (e.g., showing a prompt if notifications are disabled).

1. First, declare the global notificationStatus object

1
2
3
4
5
6
enum NotificationStatusType {
     case authorized
     case denied
     case notDetermined
}
var notificationStatus: Observable<NotificationStatusType?> = Observable(nil)

The four states of notificationStatus/NotificationStatusType correspond to:

  • nil = Object initialization… detecting…

  • notDetermined = User has not been asked whether to receive notifications

  • authorized = User has been asked whether to receive notifications and pressed “Allow”

  • denied = The user has been asked whether to receive notifications and selected “Don’t Allow”

2. Method to Check Notification Permission Status:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func checkNotificationPermissionStatus() {
    if #available(iOS 10.0, *) {
        UNUserNotificationCenter.current().getNotificationSettings { (settings) in
            DispatchQueue.main.async {
                // Note! Switch back to the main thread
                if settings.authorizationStatus == .authorized {
                    // Allowed
                    notificationStatus.value = NotificationStatusType.authorized
                } else if settings.authorizationStatus == .denied {
                    // Not allowed
                    notificationStatus.value = NotificationStatusType.denied
                } else {
                    // Not asked
                    notificationStatus.value = NotificationStatusType.notDetermined
                }
            }
        }
    } else {
        if UIApplication.shared.currentUserNotificationSettings?.types == []  {
            if let iOS9NotificationIsDetermined = UserDefaults.standard.object(forKey: "iOS9NotificationIsDetermined") as? Bool,iOS9NotificationIsDetermined == true {
                // Not asked
                notificationStatus.value = NotificationStatusType.notDetermined
            } else {
                // Not allowed
                notificationStatus.value = NotificationStatusType.denied
            }
        } else {
            // Allowed
            notificationStatus.value = NotificationStatusType.authorized
        }
    }
}

It’s not over yet!
Sharp-eyed readers might have noticed the custom UserDefaults “iOS9NotificationIsDetermined” in the ≤ iOS 9 check. So, what is it used for?

The main reason is that the method for checking push notification permissions in iOS 9 and earlier can only determine the current permissions. If it returns empty, it means no permission, but it will also be empty if the user has never been asked. This causes a problem: has the user never been asked, or have they been asked and denied permission?

Here, I used a custom UserDefaults key iOS9NotificationIsDetermined as a toggle, and added the following in appDelegate’s didRegisterUserNotificationSettings:

1
2
3
4
5
6
//appdelegate.swift:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
    // iOS 9 and below: This method is triggered after the permission prompt is shown and the user taps Allow or Don't Allow
    UserDefaults.standard.set("iOS9NotificationIsDetermined", true)
    checkNotificationPermissionStatus()
}

After setting up the notification permission status object and detection method, we still need to add the following in appDelegate…

1
2
3
4
5
6
7
8
//appdelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {  
  checkNotificationPermissionStatus()
  return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
  checkNotificationPermissionStatus()
}

How to recheck the push notification status when the app starts or returns from the background?

That concludes the inspection part. Next, let’s see how to handle requests for notification permissions if not yet asked.

3. Request Notification Permission:

1
2
3
4
5
6
7
8
9
10
11
12
13
func requestNotificationPermission() {
    if #available(iOS 10.0, *) {
        let permissiones:UNAuthorizationOptions = [.badge, .alert, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: permissiones) { (granted, error) in
            DispatchQueue.main.async {
                checkNotificationPermissionStatus()
            }
        }
    } else {
        application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil))
        // The didRegisterUserNotificationSettings in appdelegate.swift will handle the subsequent callback
    }
}

All checks and requirements are completed. Let’s see how to apply them.

4. Applications (Static)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if notificationStatus.value == NotificationStatusType.authorized {
    //OK!
} else if notificationStatus.value == NotificationStatusType.denied {
    //Not allowed
    //This example shows an UIAlertController prompt that, when tapped, navigates to the settings page
    let alertController = UIAlertController(
        title: "Dear user, you cannot receive notifications currently",
        message: "Please enable notifications for the app.",
        preferredStyle: .alert)
    let settingAction = UIAlertAction(
        title: "Go to Settings",
        style: .destructive,
        handler: {
            (action: UIAlertAction!) -> Void in
            if let bundleID = Bundle.main.bundleIdentifier, let url = URL(string:UIApplicationOpenSettingsURLString + bundleID) {
                UIApplication.shared.openURL(url)
            }
    })
    let okAction = UIAlertAction(
        title: "Cancel",
        style: .default,
        handler: {
            (action: UIAlertAction!) -> Void in
            //well....
    })
    alertController.addAction(okAction)
    alertController.addAction(settingAction)
    self.present(alertController, animated: true) {
        
    }
} else if notificationStatus.value == NotificationStatusType.notDetermined {
    //Not asked yet
    requestNotificationPermission()
}

Please note!! Do not use this when navigating to the app’s “Settings” page

UIApplication.shared.openURL(URL(string:”App-Prefs:root=\ (bundleID)”) )

Method redirects, will be rejected! will be rejected! will be rejected! (personal experience)

This is a Private API

5. Applications (Dynamic)

For dynamically changing the status, since the notificationStatus object we use is Observable, we can add a listener in viewDidLoad where we need to monitor the status continuously:

1
2
3
4
5
6
7
8
9
10
override func viewDidLoad() {
   super.viewDidLoad()
   notificationStatus.afterChange += { oldStatus,newStatus in
      if newStatus == NotificationStatusType.authorized {
       //print("❤️Thank you for enabling notifications") 
      } else if newStatus == NotificationStatusType.denied {
       //print("😭Oh no")
      }
   }
}

The above is just sample code; actual application and triggers can be adjusted as needed.

*notificationStatus uses Observable. Please pay attention to memory management: release it when necessary (to prevent memory leaks) and retain it when needed (to avoid losing event listening).

Complete Demo Product Attached:

[結婚吧APP](https://itunes.apple.com/tw/app/%E7%B5%90%E5%A9%9A%E5%90%A7-%E4%B8%8D%E6%89%BE%E6%9C%80%E8%B2%B4-%E5%8F%AA%E6%89%BE%E6%9C%80%E5%B0%8D/id1356057329?ls=1&mt=8){:target="_blank"}

結婚吧APP

Since our project supports iOS 9 to iOS 12, iOS 8 has not been tested and its support level is uncertain

If you have any questions or suggestions, feel free to contact me.


Buy me a beer

This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.

Improve this page on Github.

This post is licensed under CC BY 4.0 by the author.