ZhgChg.Li

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

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

Independent writing, free to read — please support these ads

 

Advertise here →

Solution for Handling Notification Permission Status and Requests Compatible with iOS 9 to iOS 12

What does it 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 improvements made in the previous Murmur section, a new requirement has arisen:

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

  2. After navigating to the Settings page, if the user enables or disables notifications, returning to the app should update the status accordingly.

  3. If push notification permission has never been requested, ask for permission; if it has been requested but denied, show a prompt; if it has been requested and allowed, proceed with the operation.

  4. Support for iOS 9 to iOS 12

1 to 3 are fine. Using the UserNotifications framework from iOS 10 onward can handle most cases well. The tricky part is item 4: supporting iOS 9. Handling iOS 9 requires using the old registerUserNotificationSettings method, which is not easy. Let’s take it step by step!

Approach and Structure:

Independent writing, free to read — please support these ads

 

Advertise here →

First, declare a global notificationStatus object to store the notification permission status, and 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 launches), applicationDidBecomeActive (when returning from background), and didRegisterUserNotificationSettings (for push notification requests on ≤ iOS 9).
Pages that need to respond will trigger corresponding actions (e.g., showing an alert if notifications are disabled).

1. First, declare a global notificationStatus object

enum NotificationStatusType {
     case authorized
     case denied
     case notDetermined
}
var notificationStatus: Observable<NotificationStatusType?> = Observable(nil)

The four states of notificationStatus/NotificationStatusType correspond to:

  • nil = Object initializing… checking…

  • notDetermined = The user has not been asked whether to receive notifications

  • authorized = The user has been asked about receiving notifications and tapped “Allow”

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

2. Build a method to check notification permission status:

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 {
                    // Denied
                    notificationStatus.value = NotificationStatusType.denied
                } else {
                    // Not asked yet
                    notificationStatus.value = NotificationStatusType.notDetermined
                }
            }
        }
    } else {
        if UIApplication.shared.currentUserNotificationSettings?.types == []  {
            if let iOS9NotificationIsDetermined = UserDefaults.standard.object(forKey: "iOS9NotificationIsDetermined") as? Bool,iOS9NotificationIsDetermined == true {
                // Not asked yet
                notificationStatus.value = NotificationStatusType.notDetermined
            } else {
                // Denied
                notificationStatus.value = NotificationStatusType.denied
            }
        } else {
            // Allowed
            notificationStatus.value = NotificationStatusType.authorized
        }
    }
}

Not finished yet!
Sharp-eyed readers might have noticed the custom UserDefaults key “iOS9NotificationIsDetermined” in the ≤ iOS 9 check. So, what is it used for?

The main reason is that in iOS 9 and below, the method to check push notification permissions can only determine the current granted permissions; if it’s empty, it means no permission. However, it will also be empty if the user has never been asked before. This causes a problem: has the user never been asked, or have they been asked and denied?

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

//appdelegate.swift:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
    // For iOS 9 and below, this method is triggered after the permission alert 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 methods, we also need to add the following in appDelegate…

//appdelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {  
  checkNotificationPermissionStatus()
  return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
  checkNotificationPermissionStatus()
}

How to check the push notification status again when the app launches or returns from the background?

That covers the detection part. Next, let’s see how to handle requesting notification permission if it has not been asked yet.

3. Request Notification Permission:

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 previous appdelegate.swift's didRegisterUserNotificationSettings will handle the subsequent callback
    }
}

Both detection and request are done. Let’s see how to apply them.

4. Application (Static)

if notificationStatus.value == NotificationStatusType.authorized {
    //OK!
} else if notificationStatus.value == NotificationStatusType.denied {
    //Not allowed
    //This example shows an UIAlertController prompt that, when tapped, can navigate to the settings page
    let alertController = UIAlertController(
        title: "Dear user, you currently cannot receive notifications",
        message: "Please enable notification permissions 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 when navigating to the APP’s “Settings” page

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

Using this method to redirect will cause app rejection! Will cause app rejection! Will cause app rejection! (Personal experience)

This is a Private API

5. Application (Dynamic)

For dynamically changing the status, since the notificationStatus object we use is Observable, we can add an observer in the viewDidLoad where we need to monitor the status constantly:

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 use and triggers can be adjusted as needed.

*When using Observable with notificationStatus, pay attention to memory management: release when necessary (to prevent memory leaks) and retain when needed (to avoid losing observation).

Complete Demo Project Included:

Independent writing, free to read — please support these ads

 

Advertise here →

結婚吧APP

Wedding App

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

Improve this page
Edit on GitHub
Also published on Medium
Read the original
Share this essay
Copy link · share to socials
ZhgChgLi
Author

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

Comments