带你 9 分钟掌握 iOS 10 新的本地通知框架

本文翻译自 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 第一次请求授权时,系统会为用户显示一个弹窗,然后他们就可以在设置里管理权限了:

有四种通知类型,badgesoundalertcarPlay,可以按需组合。例如想要 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,在通知中传递自定义信息。
  • attachmentsUNNotificationAttachment对象数组。用于包括音频、图像或视频内容。

注意,本地化 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对象。系统定义的标识符 UNNotificationDefaultActionIdentifierUNNotificationDismissActionIdentifier分别对应用户点击通知以打开 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 课程:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容