本文翻译自 Local Notifications with iOS 10
iOS 10 中 苹果已弃用 UILocalNotification
,是时候熟悉一下新的通知框架了。
设置
本文内容翔实,所以先从简单的开始——引入新的通知框架:
// Swift
import UserNotifications
// Objective-C (with modules enabled)
@import UserNotifications;
通过 shared UNUserNotificationCenter
对象来管理通知:
// Swift
let center = UNUserNotificationCenter.current()
// Objective-C
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
授权
和原来的通知框架一样,需要用户许可 App 会用到的通知类型。在 App 生命周期前期请求许可,例如在 application:didFinishLaunchingWithOptions:
里面。App 第一次请求授权时,系统会为用户显示一个弹窗,然后他们就可以在设置里管理权限了:
有四种通知类型,badge
,sound
,alert
,carPlay
,可以按需组合。例如想要 alert 和 sound:
// Swift
let options: UNAuthorizationOptions = [.alert, .sound];
// Objective-C
UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound;
使用 shared 通知中心来进行实际的授权:
// Swift
center.requestAuthorization(options: options) {
(granted, error) in
if !granted {
print("Something went wrong")
}
}
// Objective-C
[center requestAuthorizationWithOptions:options
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!granted) {
NSLog(@"Something went wrong");
}
}];
框架回调时带有一个布尔值,表示是否授予访问权限,还带有一个 error 对象,如果没有错误发生,error 就是 nil
。
注意:用户随时都可以更改我们的 App 的通知设置。可以用 getNotificationSettings
检查被允许的设置。它会异步回调,带有一个 UNNotificationSettings
对象,可以用来检查授权状态或个别通知设置:
// Swift
center.getNotificationSettings { (settings) in
if settings.authorizationStatus != .authorized {
// 不允许通知
}
}
// Objective-C
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
if (settings.authorizationStatus != UNAuthorizationStatusAuthorized) {
// 不允许通知
}
}];
创建通知请求
UNNotificationRequest
通知请求包含内容和触发条件:
通知内容
通知的内容是 UNMutableNotificationContent
的实例,根据需要设置下面的属性:
-
title
:字符串,描述 alert 的主要原因。 -
subtitle
:字符串,alert 的子标题(如果需要的话) -
body
:字符串,alert 的消息文本。 -
badge
:在 App 图标上显示的数字。 -
sound
:alert 送达的时候播放的声音。使用UNNotificationSound.default()
,或用文件创建自定义的声音。 -
launchImageName
:如果 App 从通知启动,用来作为启动图的图片名。 -
userInfo
:dictionary,在通知中传递自定义信息。 -
attachments
:UNNotificationAttachment
对象数组。用于包括音频、图像或视频内容。
注意,本地化 alert 的字符串(如标题)时,最好用 localizedUserNotificationString(forKey:arguments:)
,会将本地化加载延迟到到通知被送达时。
简单的例子:
// Swift
let content = UNMutableNotificationContent()
content.title = "别忘了"
content.body = "买点牛奶"
content.sound = UNNotificationSound.default()
// Objective-C
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.title = @"别忘了";
content.body = @"买点牛奶";
content.sound = [UNNotificationSound defaultSound];
通知触发器(Trigger)
根据时间、日历或位置触发通知。触发器可以重复:
- 时间间隔(Time internal):把通知安排在固定的秒数后。例如五分钟后触发:
// Swift
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 300,
repeats: false)
// Objective-C
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:300
repeats:NO];
- 日历(Calendar):在指定的日期和时间触发。使用 date components 对象创建触发器,更容易实现确定的重复间隔。使用 current calendar 以将
Date
转换为 date components。例如:
// Swift
let date = Date(timeIntervalSinceNow: 3600)
let triggerDate = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second,], from: date)
// Objective-C
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:3600];
NSDateComponents *triggerDate = [[NSCalendar currentCalendar]
components:NSCalendarUnitYear +
NSCalendarUnitMonth + NSCalendarUnitDay +
NSCalendarUnitHour + NSCalendarUnitMinute +
NSCalendarUnitSecond fromDate:date];
从 date components 创建触发器:
// Swift
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate,
repeats: false)
// Objective-C
UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:triggerDate
repeats:NO];
要创建以一定间隔重复的触发器,应使用正确的 date components 集。例如,每天在相同时间重复通知,我们只需要小时、分钟和秒:
let triggerDaily = Calendar.current.dateComponents([hour,.minute,.second,], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: true)
每周的相同时间重复,我们还需要 weekday:
let triggerWeekly = Calendar.current.dateComponents([.weekday,hour,.minute,.second,], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerWeekly, repeats: true)
过去的通知框架有一个 fire date 和 repeat interval,这个改变很有意思。date components 更灵活,但要完全复制旧框架的 fireDate 属性,没有一种简单的方式。例如,我想有一个触发器,在一周后开始每日重复。我会在未来的文章中寻找一些变通的方式。
- 位置:用户进入或离开某个物理区域时触发。区域使用 CoreLocation 的
CLRegion
指定:
// Swift
let trigger = UNLocationNotificationTrigger(triggerWithRegion:region, repeats:false)
// Objective-C
UNLocationNotificationTrigger *locTrigger = [UNLocationNotificationTrigger triggerWithRegion:region repeats:NO];
调度
Content 和 trigger 都准备好了,创建一个通知请求并将其添加到通知中心。每个通知请求都需要字符串标识符,用于将来引用:
// Swift
let identifier = "UYLLocalNotification"
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if let error = error {
// Something went wrong
}
})
// Objective-C
NSString *identifier = @"UYLLocalNotification";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
content:content trigger:trigger]
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(@"Something went wrong: %@",error);
}
}];
注意:用相同的标识符字符串调用 addNotificationRequest
会替换现存的通知。如果想要调度多个请求,每次都要使用不同的标识符。
自定义操作
要在通知中为用户添加自定义操作,首先需要创建和注册通知 category。category 定义了可以具有一个或多个操作的通知类型。举一个实际的例子,创建有两个操作的 category。有三个关键步骤:
- 定义通知操作:需要唯一的标识符和标题(最好是本地化的)。通知操作选项可以要求用户解锁设备、添加 destructive 选项或在前台启动 App。
// Swift
let snoozeAction = UNNotificationAction(identifier: "Snooze",
title: "Snooze", options: [])
let deleteAction = UNNotificationAction(identifier: "UYLDeleteAction",
title: "Delete", options: [.destructive])
// Objective-C
UNNotificationAction *snoozeAction = [UNNotificationAction actionWithIdentifier:@"Snooze"
title:@"Snooze" options:UNNotificationActionOptionNone];
UNNotificationAction *deleteAction = [UNNotificationAction actionWithIdentifier:@"Delete"
title:@"Delete" options:UNNotificationActionOptionDestructive];
我没有举这个例子——但还可以创建文本输入操作。详细信息可以参阅 UNTextInputNotificationAction
。
- 用操作创建 category:这需要另一个唯一标识符(应该在枚举中定义这些字符串):
// Swift
let category = UNNotificationCategory(identifier: "UYLReminderCategory",
actions: [snoozeAction,deleteAction],
intentIdentifiers: [], options: [])
// Objective-C
UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"UYLReminderCategory"
actions:@[snoozeAction,deleteAction] intentIdentifiers:@[]
options:UNNotificationCategoryOptionNone];
NSSet *categories = [NSSet setWithObject:category];
- 用通知中心注册 category。建议在 App 生命周期前期完成此操作:
// Swift
center.setNotificationCategories([category])
// Objective-C
[center setNotificationCategories:categories];
要在通知中包含这些操作,需要在通知内容中设置该 category:
// Swift
content.categoryIdentifier = "UYLReminderCategory"
// Objective-C
content.categoryIdentifier = @"UYLReminderCategory";
自定义操作现在作为用户通知的一部分被显示出来了:
最多可以显示四个操作,但由于屏幕空间的限制,用户可能无法全部看见。下一节中会说明,在用户选择自定义操作时,如何响应它们。
通知代理
如果在 App 位于前台时,希望响应可操作通知或接收通知,需要实现 UNUserNotificationCenterDelegate
。该协议定义了两个可选方法:
-
userNotificationCenter(_:willPresent:withCompletionHandler:)
通知被送到位于前台的 App 时调用。接收UNNotification
对象,它包含了原始的UNNotificationRequest
。使用要显示的UNNotificationPresentationOptions
进行回调(使用 .none 以忽略 alert)。 -
userNotificationCenter(_:didReceive:withCompletionHandler:)
用户选择了已送达的通知中某个操作时调用。接收UNNotificationResponse
对象,它包含了用户操作的actionIdentifier
以及UNNotification
对象。系统定义的标识符UNNotificationDefaultActionIdentifier
和UNNotificationDismissActionIdentifier
分别对应用户点击通知以打开 App 和滑动使通知消失。
这两种情况下都必须在处理完后立即进行回调。
可以借助 app delegate,但我更喜欢创建单独的类。这可能是最简化的通知代理:
class UYLNotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// 播放声音并为用户显示 alert
completionHandler([.alert,.sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// 确定用户操作
switch response.actionIdentifier {
case UNNotificationDismissActionIdentifier:
print("Dismiss Action")
case UNNotificationDefaultActionIdentifier:
print("Default")
case "Snooze":
print("Snooze")
case "Delete":
print("Delete")
default:
print("Unknown action")
}
completionHandler()
}
}
App 结束启动前一定要设置好代理。例如,在 application delegate 方法 didFinishLaunchingWithOptions
中:
// *不要*忘记保留对 delegate 的引用
let notificationDelegate = UYLNotificationDelegate()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let center = UNUserNotificationCenter.current()
center.delegate = notificationDelegate
// ...
return true
}
管理待处理的已送达的通知
老的 UILocalNotification
框架一直可以移除某个或全部待处理的通知。新的 UserNotifications
框架允许管理尚未显示在 Notification Center 中的待处理请求和已发送的通知,大大扩展了该框架。每个都有三种方法,较为类似:
getPendingNotificationRequests:completionHandler:
getDeliveredNotificationRequests:completionHandler:
两个方法都在回调中返回对象数组。对于 pending requests,得到 UNNotificationRequest
对象数组。对于 delivered notifications,得到 UNNotification
对象数组,其包含原始的 UNNotificationRequest
和送达日期。
removePendingNotificationRequests:withIdentifiers:
removeDeliveredNotifications:withIdentifiers:
移除待处理的请求和已送达的通知。给这些方法传一个数组,包含用于调度通知的字符串标识符。如果首先取回了 pending requests 或 delivered notifications,标识符就是 UNNotificationRequest
对象的一个属性。
removeAllPendingNotificationRequests
removeAllDeliveredNotifications
看方法的名字就可以知道,它们用于移除所有待处理的请求和已送达的通知。
在 shared notification center 上调用这些方法。例如要移除所有 pending notification requests:
// Swift
center.removeAllPendingNotificationRequests()
// Objective-C
[center removeAllPendingNotificationRequests];
延伸阅读
要了解更多信息,以及我没有提到的一些新功能,例如 notification service extensions 和 notification content extensions,可以看看下面的 WWDC 课程:
- WWDC 2016 Session 707 Introduction to Notifications
- WWDC 2016 Session 708 Advanced Notifications