Notification Center 与 UNUserNotification
重新温习一下两个截然不同的 notification——用于内部消息传递的 Notification Center 和 iOS 应用推送 User Notification。
一、介绍
“notification”意为“通知”,即由一方向另一方发出一个提醒,告诉对方某些事情已经完成,或者已经达到某个状态等。在实际生活中,这种消息通知的模式随处可见:快递小哥发短信说有快递到楼下了、外卖小哥打电话说有外卖到楼下了、老板通知你下午要开会,等等。我们的大脑在接收并处理这些通知之后,又会发出“通知”协调我们的身体执行相应的动作:大脑通知脚要下楼了、大脑通知脚要下楼了、大脑通知手要开始写报告了,等等。最终,这些通知可能会到达某一个终端,并触发其执行相应的动作。
在 iOS 开发中,根据通知的发送方和接收方的不同,大致有两种不同类型的通知:
- 应用发给用户的通知
- 系统发给开发者的通知
第一种通知是所有智能手机用户都非常熟悉的推送,例如在特定的时间 App 会弹出推送框向用户推送消息;第二种通知是在开发过程中使用的,开发者可以向一个“通知管理者”注册某个通知,告诉“管理者”「我需要向哪些对象发送通知」,然后在必要的时候向“管理者”发送相应的通知即可,“通知管理者”会向注册时登记的对象发送相应的通知。
二、用于开发的通知
本节讨论用于开发的通知。
Notification 是 iOS 系统下重要的消息传递机制之一,来自系统的通知封装了不同的事件信息,而自定义的通知的内容可以根据实际需要来设定。
Notification 介绍
系统内部进行消息传递的通知,实现了观察者模式。iOS 的实现方式是需要在通知中心(NotificationCenter)中注册通知,告诉通知中心需要向哪些对象发送通知,然后在需要的时候 post 相应的通知即可。对于接收通知的对象,可以选择仅接收自己感兴趣的通知。区分不同通知的方法是采用字符串或枚举作为通知的 id。
NotificationCenter
NotificationCenter 是一种通知的分发机制,可以向已注册的观察者进行信息的广播(A notification dispatch mechanism that enables the broadcast of information to registered observers)。
官方文档解读
Apple Developer 介绍了几个 NotificationCenter 类的基础用法。
- (1)获得默认的通知中心:
NotificationCenter.default
NotificationCenter 类有一个名为 default 的类属性,用于获取进程默认的通知中心。每个线程有一个独立的 Notification Center。
⚠️注意点:
- 通知中心分发给观察者处理采用同步机制,也就是说,当某一对象发送一个通知时,会一直阻塞在发送方法内,直到通知中心将该通知分发给所有观察者并且全部成功处理返回后,发送者才能执行其所在线程内的后续代码。如果需要异步发送通知,可以使用 NotificationQueue(后面提及)。
- 在多线程的应用程序中,通知总是在发送的线程中传送,这个线程可能不同于观察者注册所在的线程。
- (2)添加与移除观察者(接收通知的对象):
func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?)
func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol
func removeObserver(_ observer: Any)
func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object anObject: Any?)
参数说明:
- observer:消息的接收对象
- selector:接收对象接收到通知后执行的方法,需要使用@objc进行修饰。如果需要在通知的时候进行数据通信,selector 需要有一个 Notification 类型的参数。
- name:通知名称,用于区分不同的通知,接收者仅接收这个名字的通知。实际上是一个关联值为 String 的 enum,通过
Notification.Name.init()
创建。 - object:消息的来源,接收者只接受从这个发送者发出来的消息。设置为 nil 表明接收对象接收所有来源的某个通知。
- queue:将 block 参数添加到哪一个 OperationQueue 上,如果为 nil,block 会在 post 的线程上同步执行。
- block:接收对象接收到通知后执行的闭包,逃逸闭包,基本上和 selector 相同,但是更加 Swifty 一些。该 block 会被复制到到通知中心,直至 observer 被移除。
- (3)发送通知:
func post(Notification)
func post(name: NSNotification.Name, object: Any?, userInfo: [AnyHashable : Any]?)
func post(name: NSNotification.Name, object: Any?)
参数含义与 addObserver 基本相同,多出来的 userInfo 是用过数据通信的参数,在接受者的 selector/block 会有一个 Notification 类型的参数,通过参数的 userInfo 属性获取到通知发过来的参数。
注意:在 post 的时候并没有显示指明哪些对象接收通知,所有存在的 MyObserver 实例都会收到这个通知。因此示例代码会引发一个 warning,提示 observers 变量被写入(置空)但未被读取。
自定义通知的注册和响应
通知类型其实就是一个字符串,我们也可以使用自己定义的通知,同时也可以通过 userInfo 参数传递用户自定义数据。
Example code:
class MyObserver {
var name: String = ""
init(name: String) {
self.name = name
// 设置通知名称
let notificationName = NSNotification.Name(rawValue: "DownloadImageNotification")
// 添加观察者
NotificationCenter.default.addObserver(self, selector: #selector(downloadImage(notification:)), name: notificationName, object: nil)
}
@objc private func downloadImage(notification: Notification) {
// 使用 notification 参数进行数据通信
let userInfo = notification.userInfo as! [String:Any]
let value1 = userInfo["value1"] as! String
let value2 = userInfo["value2"] as! Int
print("\(name)获取到通知,用户数据是[\(value1), \(value2)]")
sleep(3)
print("\(name)执行完毕")
}
deinit {
// 移除观察者
NotificationCenter.default.removeObserver(self)
print("删除\(name)")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create custom observers
var observers = [MyObserver(name: "观察器1")]
print("发送通知")
let notificationName = Notification.Name(rawValue: "DownloadImageNotification")
// 发送同名通知
NotificationCenter.default.post(name: notificationName, object: self, userInfo: ["value1":"用户名", "value2":12345])
print("通知完毕")
observers = []
}
}
运行结果:
可以看到,在主线程调用 post 发出通知后,会立即在 post 的线程上同步执行 selector。如果在主线程上发出通知后执行的操作比较耗费时间,需要将耗时任务分到异步线程中执行,例如:
@objc private func downloadImage(notification: Notification) {
// 使用 GCD 将任务放入全局异步线程中
DispatchQueue.global().async {
// userInfo is used to transfer user defined data
let userInfo = notification.userInfo as! [String:Any]
let value1 = userInfo["username"] as! String
let value2 = userInfo["id"] as! Int
print("\(self.name)获取到通知,用户名:\(value1), 密码:\(value2)")
sleep(3)
print("\(self.name)执行完毕")
}
}
运行结果变为下图,此时主线程没有被阻塞。:
系统通知的注册和响应
除了自定义的通知以外,还可以使对象接收 iOS 系统发来的通知。系统通知实际上是一个有着特殊名字的通知,Notification.Name 的枚举值包含了一系列系统通知,具体可用选项比较多,请参考官方文档
Example code:
// create an instance of notification center
let nc = NotificationCenter.default
// fetch the main queue
let queue = OperationQueue.main
// add an observer which listens to the UIApplicationDidEnterBackground notification.
let observer = nc.addObserver(forName: .UIApplicationDidEnterBackground, object: nil, queue: queue) {
noti in
print("Entering background...")
}
可以看到接收系统通知和接收自定义通知基本上是一样的。
NotificationQueue
在 NotificationCenter 中,post 出去的通知会马上到达 observer 手中。如果我们需要使通知延迟一段时间后再进行广播,可以使用 NotificationQueue 对 Notification 进行管理。NotificationQueue 维护一个队列,enqueue 的通知不会立刻发送到 observer,而是在 dequeue 的时候将满足条件的 Notification 按先进先出的顺序逐个进行发送。
官方文档解读
- (1)创建队列:
NotificationQueue.init(notificationCenter: NotificationCenter)
可以将当前线程的通知发送到另一个线程上。
- (2)获得默认队列:
NotificationQueue.default
获得当前线程的默认通知队列。一般来说一个线程拥有一个通知中心、维护一个通知队列。
- (3)通知入列与出列:
func enqueue(Notification, postingStyle: NotificationQueue.PostingStyle, coalesceMask: NotificationQueue.NotificationCoalescing, forModes: [RunLoop.Mode]?)
func enqueue(Notification, postingStyle: NotificationQueue.PostingStyle)
func dequeueNotifications(matching: Notification, coalesceMask: Int)
参数说明:
- matching:用于进行判断的“模板”通知。
- postingStyle:枚举值,指明发送通知的时间,可以是 asap、whenIdle、now 三者之一,分别表示当前通知回调结束时、线程空闲时、立刻发送。
- coalesceMask:屏蔽位,指明屏蔽通知的方式。0 表示不屏蔽,奇数表示屏蔽与 matching 同名的通知,偶数表示屏蔽与 matching 同发送者的通知。
- forModes:规定只能在 RunLoop 处于哪些模式中的时候才能发送通知。
在 dequeue 的时候,通知是按照 enqueue 的顺序出来的,如果需要屏蔽的话,通知中心就不会将被屏蔽的通知广播出去。
示例代码
import UIKit
import NotificationCenter
class MyObserver {
var name: String = ""
init(name: String) {
self.name = name
// 设置通知名称
let notiName1 = Notification.Name(rawValue: "StatusNotification")
let notiName2 = Notification.Name(rawValue: "DownloadImageNotification")
let notiName3 = Notification.Name(rawValue: "AlertNotification")
NotificationCenter.default.addObserver(self, selector: #selector(statusCheck(notification:)), name: notiName1, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(downloadImage(notification:)), name: notiName2, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleAlert(notification:)), name: notiName3, object: nil)
}
@objc private func statusCheck(notification: Notification) {
// userInfo is used to transfer user defined data
let userInfo = notification.userInfo as! [String:Any]
guard let type = userInfo["type"] as? String, let username = userInfo["username"] as? String, let userID = userInfo["id"] as? Int else {
print("Error in parsing parameters")
return
}
print("\(self.name)获取到通知\(notification.name)")
print("类型:\(type), 用户名:\(username), ID:\(userID)")
print("\(self.name)执行完毕\(notification.name)")
print("--------------------")
sleep(2)
}
@objc private func downloadImage(notification: Notification) {
// userInfo is used to transfer user defined data
let userInfo = notification.userInfo as! [String:Any]
guard let imageName = userInfo["imageName"] as? String, let url = userInfo["url"] as? String else {
print("Error in parsing parameters")
return
}
print("\(self.name)获取到通知\(notification.name)")
print("图片名:\(imageName), 链接:\(url)")
print("\(self.name)执行完毕\(notification.name)")
print("--------------------")
sleep(2)
}
@objc private func handleAlert(notification: Notification) {
// userInfo is used to transfer user defined data
let userInfo = notification.userInfo as! [String:Any]
guard let reason = userInfo["reason"] as? String, let code = userInfo["code"] as? Int else {
print("Error in parsing parameters")
return
}
print("\(self.name)获取到通知\(notification.name)")
print("原因:\(reason), 错误代码:\(code)")
print("\(self.name)执行完毕\(notification.name)")
print("--------------------")
sleep(2)
}
deinit {
// 在 deinit 中移除观察者
NotificationCenter.default.removeObserver(self)
print("删除\(name)")
}
}
class ViewController: UIViewController {
// create custom observers
let observers = [MyObserver(name: "观察器1")]
override func viewDidLoad() {
super.viewDidLoad()
print("发送通知")
let notiName1 = Notification.Name(rawValue: "StatusNotification")
let notiName2 = Notification.Name(rawValue: "DownloadImageNotification")
let notiName3 = Notification.Name(rawValue: "AlertNotification")
let noti1 = Notification(name: notiName1, object: self, userInfo: ["type":"log in", "username":"Andy", "id":12345])
let noti2 = Notification(name: notiName2, object: self, userInfo: ["imageName":"Apple", "url":"https://www.apple.com"])
let noti3 = Notification(name: notiName3, object: self, userInfo: ["reason":"Page Not Found", "code":404])
let noti4 = Notification(name: notiName1, object: self, userInfo: ["type":"log out", "username":"Andy", "id":12345])
print("入列三个通知")
let queue = NotificationQueue.default
queue.enqueue(noti1, postingStyle: .whenIdle)
queue.enqueue(noti2, postingStyle: .whenIdle)
queue.enqueue(noti3, postingStyle: .whenIdle)
print("入列完毕")
print("出列三个通知")
queue.dequeueNotifications(matching: noti1, coalesceMask: 0)
queue.dequeueNotifications(matching: noti2, coalesceMask: 0)
queue.dequeueNotifications(matching: noti3, coalesceMask: 0)
print("出列完毕")
// 对比同步 post
NotificationCenter.default.post(noti4)
print("通知完毕")
}
}
为方便起见,在代码中仅设置了一个 observer 对多个通知进行监听,将三个不同名的通知入列,规定在线程空闲的时候才发送通知,并且全部不屏蔽地发送。为进行对比,在出列完毕后增加了一个普通的同步 post。
运行结果如下:
可以看到,在 enqueue 通知后,接收者没有马上收到消息,而是在 dequeue 通知后并且达到 postingStyle 设置的时机才会统一发送通知,而作为对比的同步 post 是马上阻塞当前线程发送通知。如果将 postingStyle 设置为 .now 的话,会跟普通的 post 一样马上发送通知。coalesceMask 设置为 0 时不会进行屏蔽;如果将任意一个 coalesceMask 改成 1,对应的通知就不会发送;如果将任意一个 coalesceMask 改成 2,由于三个通知的发送者都是 self,因此 noti1、noti2、noti3 全部被屏蔽。
DistributedNotificationCenter
前面提到的 NotificationCenter 是在应用进程内向对象发送通知。Apple 提供了 DistributedNotificationCenter 类来实现不同的应用进程之间的通信。每一个应用进程都有一个默认的 DistributedNotificationCenter,用来接收其他进程发过来的通知,同时负责将本进程的通知广播到其他进程的 DistributedNotificationCenter 中。
在使用上,DistributedNotificationCenter 和进程内的 NotificationCenter 差别不大,同样是通过一个名为 default
的类实例获得默认的通知中心,通过 addObserver
和 removeObserver
添加、移除观察者,通过 post
发送通知。增加了一个 Bool 类型的 suspend
属性来设置是否挂起通知中心,不接收和发送进程间的通知。
三、向用户发送的通知
本节讨论 iOS 推送。
UserNotifications——用户通知
从 iOS 10 起,用户通知(即呈现给用户的消息推送)发生了较大变化,Apple 的目的是为了进一步简化用户通知的使用,并向开发者提供更加强大的功能,使用户通知能够呈现更多的内容。
使用 UserNotifications 创建用户推送主要包括以下几个步骤:
- Create a trigger. Depending on the situation that triggers a notification, select a different constructor.
- Create the content of the notification, using UNMutableNotificationContent.
- Create the request, using the trigger and content above.
- Add the request to the notification center, using UNUserNotificationCenter.current().add()
- Remember to ask the user for permission to show notification.
- To provide attachment, use the UNNotificationAttachment class to add attachment to the notification.
- To add an action in the notification, use UNNotificationAction and UNNotificationCategory
- Create the action for the notification
- Defines the category of the action
Just to remind: the majority of notification needs an identifier. Do not mess them up! It is recommended to use enum or at least constants to manage these identifiers.
整个过程可以用一个简单的图来说明:
获得默认通知中心
和 NotificationCenter 相同,UserNotifications 同样有一个通知中心对整个 App 的消息推送进行管理。可以用 UNUserNotificationCenter.current()
获得默认的用户通知中心。
获取用户权限
需要弹出推送的 App 需要申请获得用户权限,也就是下面这个 Alert 框:
这个框不是由开发者自己写的,而是由 iOS 系统弹出来的。可以使用通知中心的 requestAuthorization(options:completionHandler:)
申请不同的权限,包括推送弹窗、提示音、角标等,并且在回调的 handler 中处理用户的选择。
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if let error = error {
// Handle the error here.
}
// Enable or disable features based on the authorization.
if granted {
// user permit
} else {
// user deny
}
}
通常是在应用第一次打开的时候向用户请求权限,因此通常是在 AppDelegate 的方法中调用。
检查当前的提醒设置
用户可能会随时更改提醒的权限,比如突然不想接收 App 的提醒了,这时再进行推送时无效的。可以使用通知中心的 getNotificationSettings(completionHandler:)
获得当前的提醒设置,例如用户是否允许弹窗等。
let center = UNUserNotificationCenter.current()
center.getNotificationSettings { settings in
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.
}
}
设置推送触发器
本地推送可以设置在某个时间或满足某些条件时向用户发送通知,这是通过设置触发器 UNNotificationTrigger
来实现的,包括四种不同的触发器:
- UNCalendarNotificationTrigger
- UNTimeIntervalNotificationTrigger
- UNLocationNotificationTrigger
- UNPushNotificationTrigger
从它们的名字可以知道,分别是在给定日期、给定时间间隔、给定位置进行推送,最后一个是手动进行推送。不同的 trigger 有不同的初始化方法,按照要求进行设置即可。
设置推送内容
通过 UNMutableNotificationContent 来设置推送显示的内容,包括标题(title)、副标题(subtitle)、主体(body)、角标数字(badge)、提示音(sound)、自定义内容(userInfo)、附件(attachments)等,其中附件可以包含文本文件、图片甚至视频,但是有一定的条件限制,详情需要查阅官方文档。
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationStringForKey("Hello!", arguments: nil)
content.body = NSString.localizedUserNotificationStringForKey("Hello_message_body", arguments: nil)
content.sound = UNNotificationSound.default()
附带一提,在 Assets.xcassets 中,有两栏用来设置 iPhone 和 iPad 上的提醒图标。
创建推送请求
设置好触发器 trigger 和推送的内容 content 后,就可以通过 UNNotificatonRequest 来创建推送请求了。不同的推送请求通过 String 类型的 identifier 来进行区分,最后将 request 添加到用户通知中心即可。
// Create the request
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString,
content: content, trigger: trigger)
// Schedule the request with the system.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
if error != nil {
// Handle any errors.
}
}
可以通过 func getPendingNotificationRequests(completionHandler: ([UNNotificationRequest]) -> Void)
获得所有还没触发的用户通知。
推送请求一旦创建,在满足触发条件之前都会保持 active 的状态。如果在创建 trigger 的时候设置了 repeats 为 true 的话。可以根据 identifier 来取消某个推送或者取消所有推送。
func removePendingNotificationRequests(withIdentifiers: [String])
func removeAllPendingNotificationRequests()
设置代理
遵循 UNUserNotificationDelegate 协议的类可以作为 UserNotification 的代理,接收并处理用户输入、如何处理推送等。Apple 提示我们需要在 App 完成启动之前完成代理的设置,令 AppDelegate 遵遁 UNUserNotificationDelegate 即可,并在 application(_:didFinishLaunchingWithOptions:)
中设置代理为 self。
UNUserNotificationDelegate 定义了三个方法:
// 用户行为响应
func userNotificationCenter(UNUserNotificationCenter, didReceive: UNNotificationResponse, withCompletionHandler: () -> Void)
// 显示本地推送
func userNotificationCenter(UNUserNotificationCenter, willPresent: UNNotification, withCompletionHandler: (UNNotificationPresentationOptions) -> Void)
func userNotificationCenter(UNUserNotificationCenter, openSettingsFor: UNNotification?)
必须设置代理之后,本地推送才能生效。
处理用户输入
有时候,我们需要让推送具有与用户交互的功能,用户在看到推送之后,只需要点按可用的选项就能够向 App 发出指令,而不需要真正进入到 App 中。通过 UNNotificationAction 和 UNNotificationCategory 相结合来声明一个能够响应用户输入的 action。
- UNNotificationAction
可以理解为通知附带的按钮,用户可以点击按钮进行交互。
// Define the custom actions.
let acceptAction = UNNotificationAction(identifier: "ACCEPT_ACTION",
title: "Accept",
options: UNNotificationActionOptions(rawValue: 0))
let declineAction = UNNotificationAction(identifier: "DECLINE_ACTION",
title: "Decline",
options: UNNotificationActionOptions(rawValue: 0))
不同的 action 通过 identifier 来进行区分;title 为按钮的标签;option 为 action 的选项,包括 authenticationRequired(仅在设备解锁的时候可以交互)、destructive(该行为可能导致不可逆的后果)、foreground(用户需要打开 App 作进一步的交互)。
- UNNotificationCategory
Category 将 action 进行分类,在创建 content 的时候设置 categoryIdentifier,可以规定哪些推送支持怎么样的行为。
// Define the notification type
let meetingInviteCategory =
UNNotificationCategory(identifier: "MEETING_INVITATION",
actions: [acceptAction, declineAction],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: "",
options: .customDismissAction)
// Register the notification type.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.setNotificationCategories([meetingInviteCategory])
// set content's category identifier
content.categoryIdentifier = "MEETING_INVITATION"
处理用户输入则是在代理方法 func userNotificationCenter(UNUserNotificationCenter, didReceive: UNNotificationResponse, withCompletionHandler: () -> Void)
中进行的,通过 response 参数的 actionIdentifier 属性来区分不同的 action,需要和创建 action 的时候定义的 identifier 完全相同才能正确识别。
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler:
@escaping () -> Void) {
// Get the meeting ID from the original notification.
let userInfo = response.notification.request.content.userInfo
let meetingID = userInfo["MEETING_ID"] as! String
let userID = userInfo["USER_ID"] as! String
// Perform the task associated with the action.
switch response.actionIdentifier {
case "ACCEPT_ACTION":
sharedMeetingManager.acceptMeeting(user: userID,
meetingID: meetingID)
break
case "DECLINE_ACTION":
sharedMeetingManager.declineMeeting(user: userID,
meetingID: meetingID)
break
// Handle other actions…
default:
break
}
// Always call the completion handler when done.
completionHandler()
}
示例代码
最后贴一段示例代码吧(部分来源 Apple Developer 官网):
- AppDelegate.swift
import UIKit
import UserNotifications
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// 设置 UserNotifications 代理
UNUserNotificationCenter.current().delegate = self
// 请求本地推送的权限
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if let error = error {
print("Oops, we've met an error: \(error)")
}
if granted {
print("The user grants us the permission to push notifications😃")
} else {
print("The user denies our permission😣")
}
}
return true
}
}
// 代理方法
extension AppDelegate: UNUserNotificationCenterDelegate {
public func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
print(response.actionIdentifier)
completionHandler()
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// New in iOS 10, we can show notifications when app is in foreground, by calling completion handler with our desired presentation type.
print("something")
// 设置提醒的方式:.alert, .sound, etc
completionHandler(.alert)
}
}
- ViewControler.swift
import UIKit
import UserNotifications
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 设置推送内容
let content = UNMutableNotificationContent()
content.title = "This is title"
content.subtitle = "This is subtitle"
content.body = "This is body"
content.sound = UNNotificationSound.default
// 定义用户交互方式
let acceptAction = UNNotificationAction(identifier: "ACCEPT_ACTION",
title: "Accept",
options: UNNotificationActionOptions(rawValue: 0))
let declineAction = UNNotificationAction(identifier: "DECLINE_ACTION",
title: "Decline",
options: UNNotificationActionOptions(rawValue: 0))
// 定义推送能够响应的交互类别
let meetingInviteCategory =
UNNotificationCategory(identifier: "MEETING_INVITATION",
actions: [acceptAction, declineAction],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: "",
options: .customDismissAction)
// 向通知中心注册交互类别
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.setNotificationCategories([meetingInviteCategory])
content.categoryIdentifier = "MEETING_INVITATION"
// 设置一个 10 秒的触发器
// 若希望重复提醒(repeats 为 true),则两次推送的时间间隔(timeInterval)必须大于 60,否则会崩溃。
let tenSecondsTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(identifier: "tenSeconds", content: content, trigger: tenSecondsTrigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print(error)
} else {
print("Notification request added.")
}
}
}
}
五、总结
本篇文章对 NotificationCenter 和 UNUserNotification 进行了学习,对于我来说认识了「观察者模式」,并且知道了如何根据条件对用户发起推送。需要注意的点是 NotificationCenter 和 UNUserNotification 都非常依赖于字面量的 identifier 区分不同的通知,最好将其转换成 struct 或 enum,防止手误。
另外附上一个更加完整的 demo,将 NotificationCenter 和 UserNotifications 的功能进行简单的整合。
参考链接
- https://www.jianshu.com/p/209ef870e131
- https://juejin.im/post/59422d5861ff4b006cc66be1
- https://developer.apple.com/documentation/foundation/notificationcenter
- https://developer.apple.com/documentation/usernotifications/unusernotificationcenter