Post

iOS 9 到 iOS 12 推播通知权限状态处理|Swift 全面支援与实战范例

解决 iOS 9~12 推播通知权限判断困难,提供完整 Swift 程式码范例,实现权限动态监控与提示设定页跳转,确保使用者体验顺畅,提升推播功能成功率。

iOS 9 到 iOS 12 推播通知权限状态处理|Swift 全面支援与实战范例

Click here to view the English version of this article.

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

基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。


从 iOS 9 到 iOS 12 推播通知权限状态处理(Swift)

适配 iOS 9 ~ iOS 12 处理通知权限状态及要求权限的解决方案

做什么?

接续前一篇「 什么?iOS 12 不需使用者授权就能传送推播通知(Swift) 」提到的推播权限取得流程优化,经过上一篇Murmur部分写的优化之后又遇到了新的需求:

  1. 使用者若关闭通知功能,我们能在特定功能页面提示他去设定开启

  2. 跳转至设定页后,若有打开/关闭通知的操作,回到APP要能跟著更改状态

  3. 没询问过推播权限时询问权限,有询问过但是不允许则跳提示,有询问过又是允许则能继续操作

  4. iOS 9 ~ iOS 12 都要支援

1~3 都还好,使用 iOS 10 之后的Framework UserNotifications 差不多都能妥善的解决,麻烦的是第4项 要能支援 iOS 9,iOS 9要使用 registerUserNotificationSettings 旧的方式处理起来并不容易;就让我们一步一步做起吧!

思路及架构:

首先宣告一个全域的 notificationStatus物件 储存通知权限状态 并在需要处理的页面加上属性监听(这边我使用 Observable 做属性变化的订阅、可自行找适合的KVO或用Rx、ReactiveCocoa)

并在 appDelegate 中 didFinishLaunchingWithOptions (APP初始打开时)、applicationDidBecomeActive (从背景状态回复时)、didRegisterUserNotificationSettings (≤iOS 9 的推播询问处理) 这些方法中处理检查推播通知权限状态并更改 notificationStatus 的值 需要做处理的页面就会触发并作相对应的处理(EX: 跳出通知被关闭提示)

1. 首先宣告全域 notificationStatus 物件

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

notificationStatus/NotificationStatusType 的四种状态分别对应:

  • nil = 物件初始化…检测中…

  • notDetermined = 未询问过使用者要不要接收通知

  • authorized = 已询问过使用者要不要接收通知且按「允许」

  • denied = 已询问过使用者要不要接收通知且按「不允许」

2. 构建检测通知权限状态的方法:

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 {
                //注意!要切回主执行绪
                if settings.authorizationStatus == .authorized {
                    //允许
                    notificationStatus.value = NotificationStatusType.authorized
                } else if settings.authorizationStatus == .denied {
                    //不允许
                    notificationStatus.value = NotificationStatusType.denied
                } else {
                    //没问过
                    notificationStatus.value = NotificationStatusType.notDetermined
                }
            }
        }
    } else {
        if UIApplication.shared.currentUserNotificationSettings?.types == []  {
            if let iOS9NotificationIsDetermined = UserDefaults.standard.object(forKey: "iOS9NotificationIsDetermined") as? Bool,iOS9NotificationIsDetermined == true {
                //没问过
                notificationStatus.value = NotificationStatusType.notDetermined
            } else {
                //不允许
                notificationStatus.value = NotificationStatusType.denied
            }
        } else {
            //允许
            notificationStatus.value = NotificationStatusType.authorized
        }
    }
}

以上还没结束! 眼尖的朋友应该在≤ iOS 9的判断之中发现”iOS9NotificationIsDetermined”这个自订的UserDefaults,那它是用来干嘛的呢?

主因是≤iOS 9的检测推播权限方法只能用获取目前的权限有哪些作为判断,若为空则代表无权限,但在没询问过权限的情况下也是会是空白;这时候麻烦就来了,使用者究竟是没问过还是问过按不允许?

这边我使用了一个自订的UserDefaults iOS9NotificationIsDetermined作为判断开关,并在appDelegate的didRegisterUserNotificationSettings中加入:

1
2
3
4
5
6
//appdelegate.swift:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
    //iOS 9(含)以下,跳出询问要不要允许通知的视窗后,按下允许或不允许都会触发这个方法
    UserDefaults.standard.set("iOS9NotificationIsDetermined", true)
    checkNotificationPermissionStatus()
}

通知权限状态的物件、检测的方法都构建好后,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()
}

APP初始跟从背景返回都要再检测一次推播状态如何

以上就是检测的部分,再来我们来看如果是未询问该怎么处理要求通知权限

3. 要求通知权限:

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))
        //前面appdelegate.swift的didRegisterUserNotificationSettings会处理后续callback
    }
}

检测跟要求都处理完啰,我们来看看如何应用

4. 应用(静态)

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 {
    //不允许
    //这边范例是跳出UIAlertController提示并点击后可跳转至设定页面
    let alertController = UIAlertController(
        title: "亲爱的,您目前无法接收通知",
        message: "请开启结婚吧通知权限。",
        preferredStyle: .alert)
    let settingAction = UIAlertAction(
        title: "前往设定",
        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: "取消",
        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 {
    //未询问
    requestNotificationPermission()
}

请注意!!跳到APP的「设定」页时请勿使用

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

方式跳转, 会被退审! 会被退审! 会被退审! (亲身经历)

这是Private API

5. 应用(动态)

动态变更状态的部分,因为notificationStatus物件我们使用是Observable,我们可以在要时时监测状态的viewDidLoad中加入监听处理:

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("❤️谢谢你打开通知") 
      } else if newStatus == NotificationStatusType.denied {
       //print("😭呜呜")
      }
   }
}

以上只是范例Code,实际应用、触发可再自行调校

*notificationStatus 使用 Observable 请注意记忆体控制,该释放时要能释放(防止记忆体泄漏)、不该释放时需持有(避免监听失效)

最后附上完整Demo成品:

[结婚吧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

*由于我们的专案支援范围是iOS 9 ~ iOS12,iOS 8未进行任何测试不确定支援程度

有任何问题及指教欢迎 与我联络


Buy me a beer

本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。

Improve this page on Github.

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