User Notifications框架
- 05 Sep, 2024

User Notifications 是iOS开发中,专门用于给用户发送通知的框架。 用户通知是App与用户交互的一种方式,无论App是否在用户的设备上运行,我们都可以给他们发送通知。比如提醒用户某个需要代办的事项,推送一些天气变化的情况等。
用户通知的分类
用户通知分两种
- 本地通知:通过App创建通知,在特定的条件下,比如在特定的时间,或者特定的地理位置,向用户发送通知。
- 远程通知:也叫推送通知,由App开发者的远程服务端(Provider Server),先向苹果的Apple Push Notification service (APNs)发送消息,再由APNs把消息发送到用户的设备, 最终以通知的形式展现给用户。
用户通知的时效性和投递率
iOS系统会尽可能地按时发送本地通知和远程通知给用户,但是并不保证通知一定会送达,如果对通知有更高的要求,可以考虑使用PushKit。两种的使用场景区别如下:
- 使用 User Notifications:适用于绝大多数标准通知场景,如应用内消息、提醒、警告、新闻推送等。适用于希望通知显示在通知中心、并且允许用户与通知互动的应用场景。
- 使用 PushKit:当开发 VoIP 应用或需要高优先级、立即唤醒应用的推送时,选择 PushKit。例如,呼叫通知、紧急消息或即时通讯应用中的 VoIP 通话。
用户通知的处理步骤
使用用户通知主要分以下几步
- 请求用户授权
- 定制通知内容
- 发送通知
- 处理通知
后面将逐一介绍
请求用户授权
用户通知的形式和样式
用户通知会在锁屏界面,通知中心以及Banner位显示,并且伴随着通知的声音和App角标提示。由于用户通知可能会影响用户的交互体验,因此,想要给用户发送通知时,一定要获取用户的授权才可以。
在请求用户授权时,需要明确指明App使用的通知方式,通过 UNAuthorizationOptions , 我们可以知道用户通知的方式有如下几种
badge
: The ability to update the app’s badge.sound
: The ability to play sounds.alert
: The ability to display alerts.carPlay
: The ability to display notifications in a CarPlay environment.criticalAlert
: The ability to play sounds for critical alerts.providesAppNotificationSettings
: An option indicating the system should display a button for in-app notification settings.provisional
: The ability to post noninterrupting notifications provisionally to the Notification Center.
上面几种类型中,除了provisional
表明是设置临时的用户通知(后面会详细介绍到),其它都是控制用户通知的交互方式
用户授权分两种方式
- 明确请求授权
- 临时请求授权
明确请求用户授权
通过UNUserNotificationCenter
实例的requestAuthorization()
方法, 并指定通知的形式,比如alert
, badege
, sound
等,可以发起用户授权
func requestAuthorization() async {
do {
if try await center.requestAuthorization(options: [.alert, .badge, .sound]) {
// You have authorization.
print("You have authorization.")
} else {
// You don't have authorization.
print("You don't have authorization.")
}
} catch {
// Handle any errors.
print("error: \(error.localizedDescription)")
}
}
授权对话框仅会显示一次
当App在第一次执行请求用户授权时,会弹出类似如下的对话框,让用户做选择。
一旦用户选择了允许授权或者拒绝授权后,系统会记录下用户的选择结果,以后App代码再次请求用户授权时,不会再弹出上面的请求授权对方框,而是之前返回用户之前选择的结果。除非用户卸载并重新安装了App,不然App都不会再弹出上面的对话框。
因此首次请求用户授权的时机非常重要,一般不建议在用户一打开App的情况下就向用户发起请求,因为这个时候,用户往往不知道App需要授权的目的是什么,很容易直接拒绝授权。最佳的时机是在用户在App中进行了某种操作,需要App通过通知提醒他时,比如:用户添加了一个代办事项,App需要在指定的时间向用户发送通知提醒用户,这种情况下发起用户通知授权的请求,才容易被用户所接受。
临时请求授权
当App明确请求发送通知的权限时,用户必须在看到授权对话框时决定是否允许或拒绝权限。就算我们考虑了请求对话框的弹出时机,但用户仍然因为某些顾虑,会拒绝授权。
这种情况下可以考虑使用临时授权来临时发送通知, 临时授权不会弹出请求授权的对话框给用户,让用户做选择,但当收到通知时,也不会响起通知声音,在锁屏界面或者Banner位显示通知消息,而是仅出现在通知中心的历史记录中。这些通知还包含按钮,提示用户保留或关闭通知。如果用户按下“关闭”按钮,系统会在确认选择后拒绝应用发送更多的通知。
如果用户按下“保留”按钮,根据用户是否在系统设置的通知中开启了”Scheduled Summary”功能,提示给用户的信息也不一样。
假如用户之前在系统设置的通知中开启了”Scheduled Summary”功能,系统会提示用户在两种选项之间做出选择:立即发送或在计划摘要中发送。
选择“立即发送”后,未来的通知会安静地传递。系统会授权你的应用发送通知,但不会授予显示警报、播放声音或为应用图标添加标记的权限。通知只会出现在通知中心的历史记录中,除非用户更改通知设置。
假如用户之前在没有在系统设置的通知中开启”Scheduled Summary”功能,则只会显示”立即发送”。
相比明确请求用户授权,请求临时授权时,只需要在请求发送通知权限时添加provisional
选项就可以了。
func requestAuthorization() async {
do {
if try await center.requestAuthorization(options: [.alert, .badge, .sound, .provisional]) {
// You have authorization.
print("You have authorization.")
} else {
// You don't have authorization.
print("You don't have authorization.")
}
} catch {
// Handle any errors.
print("error: \(error.localizedDescription)")
}
}
与明确请求授权不同,这个代码不会弹出请求授权的对话框给用户,让用户选择。相反,当第一次调用此方法时,系统会自动授予权限。然而,直到用户明确选择保留或关闭通知之前,授权状态将一直保持为 UNAuthorizationStatus.provisional
。
此外,如果应用是请求临时授权,则可以在应用首次启动时请求授权。不用考虑请求的时机问题,因为用户只有在实际收到通知时,才会被在通知中心中被要求选择保留或关闭通知,并且不会影响到用户。
获取用户的授权情况
由于用户可能随时会调整App的通知权限,因此,在每次发送通知前,都最好先检查一下当前用户是否允许发送通知。通过UNUserNotificationCenter
实例的notificationSettings()
方法, 可以检查当前用户是否授权。
let center = UNUserNotificationCenter.current()
// Obtain the notification settings.
let settings = await center.notificationSettings()
// Verify the authorization status.
guard (settings.authorizationStatus == .authorized) ||
(settings.authorizationStatus == .provisional) else { return }
if settings.alertSetting == .enabled {
// Schedule an alert-only notification.
} else {
// Schedule a notification with a badge and sound.
}
通知示例
先看一个完整的发送本地通知的示例,至于发送通知的细节,我们后面会讲到。发送通知后,记得将App置于后台或者锁屏,等待5秒钟,既可以看到通知效果。
import SwiftUI
import UserNotifications
struct LocalNotification: View {
let center = UNUserNotificationCenter.current()
let authorizationOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Button("Request Explicitly Notification Permission") {
Task {
await requestAuthorization(options: authorizationOptions, provisional: false)
}
}
Button("Send Explicitly Notification") {
Task {
await sendNotification(provisional: false)
}
}
}
.padding()
.border(.black)
VStack(alignment: .leading) {
Button("Request Provisional Notification Permission") {
Task {
await requestAuthorization(options: authorizationOptions, provisional: true)
}
}
Button("Send Provisional Notification") {
Task {
await sendNotification(provisional: true)
}
}
}
.padding()
.border(.black)
Spacer()
}
.buttonStyle(.borderedProminent)
}
func createRequest() -> UNNotificationRequest {
let content = UNMutableNotificationContent()
content.title = "Notification Title"
content.subtitle = "Notification Subtitle"
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
return UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
}
func requestAuthorization(options: UNAuthorizationOptions, provisional: Bool = false) async {
do {
var options = options
if provisional {
options = options.union(.provisional)
}
if try await center.requestAuthorization(options: options) {
// You have authorization.
print("You have authorization.")
} else {
// You don't have authorization.
print("You don't have authorization.")
}
} catch {
// Handle any errors.
print("error: \(error.localizedDescription)")
}
}
func sendNotification(provisional: Bool) async {
let settings = await center.notificationSettings()
if settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional {
print("authorized: \(settings.authorizationStatus)")
// if settings.alertSetting == .enabled {
// // Schedule an alert-only notification.
// } else {
// // Schedule a notification with a badge and sound.
// }
await addNotification()
} else {
print("未授权 \(settings.authorizationStatus)")
await requestAuthorization(options: authorizationOptions, provisional: provisional)
}
}
func addNotification() async {
do {
try await center.add(createRequest())
} catch {
print("add request failed, err: \(error.localizedDescription)")
}
}
}