iOS 9 到 iOS 12 推播通知权限状态处理|Swift 全面支援与实战范例
解决 iOS 9~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部分写的优化之后又遇到了新的需求:
使用者若关闭通知功能,我们能在特定功能页面提示他去设定开启
跳转至设定页后,若有打开/关闭通知的操作,回到APP要能跟著更改状态
没询问过推播权限时询问权限,有询问过但是不允许则跳提示,有询问过又是允许则能继续操作
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成品:
*由于我们的专案支援范围是iOS 9 ~ iOS12,iOS 8未进行任何测试不确定支援程度
有任何问题及指教欢迎 与我联络 。
本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。