iOS10 中统一了本地推送和远程推送的 API,在 UserNotifications.framework 来统一处理与推送相关任务,并增加了图片、音频、视频,自定义通知 UI 等新特性。

回顾

推送的过程:app 向 apple 服务器申请注册通知,apple 服务器再向 APNs 服务器申请 token,APNs 将 token 发送给 app。app 保存这个 token 到个人服务器。在个人服务器根据 token 发送推送时,先将 payload 发送到 APNs,① 然后 APNs 根据 token 下发到对应的 app

我的理解:① iOS10 中 Service Extension & Content Extension 是在这里处理完之后再转发出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"aps" : {
"alert" : {
"title" : "message",
"subtitle" : "brief message" # new
"body" : "Your message Here"
},
"sound" : "default",
"badge" : 1
"mutable-content":1 # new
}
"image": "https://onevcat.com/assets/images/background-cover.jpg" # new
}

通知界面

页面变成如下图所示远角矩形,界面的元素有 Title、subTitle、body等,对于用户操作加了 3D Touch、Action。

多媒体

iOS10 中通知不再仅仅是一行文字这么简单,在此版本加入了图片、音频、视频这样的多媒体。对于手机碎片化时间有了更好的诠释。当然对于多媒体的大小是有限制的,对于这些多媒体的加载时间大概在 30s 左右,远程下载的多媒体下载结束之后由系统来管理,当然也可以通过 UserNotificationCenter 来管理。

多媒体类型 限制大小
图片 10M
音频 5M
视频 50M

1.创建 UNNotificationCategory 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let myCatrgory: UNNotificationCategory = {

let commentAction = UNTextInputNotificationAction(
identifier: myNotificationAction.comment.rawValue,
title: "写评论",
options: [.foreground],
textInputButtonTitle: "发送",
textInputPlaceholder: "写点什么吧")

let zanAction = UNNotificationAction(
identifier: myNotificationAction.zan.rawValue,
title: "点赞",
options: [.foreground])

let cancelAction = UNNotificationAction(
identifier: myNotificationAction.cancel.rawValue,
title: "取消",
options: [.destructive])

return UNNotificationCategory(identifier: UserNotificationCategoryType.myNotificationCategory.rawValue, actions: [commentAction, zanAction, cancelAction], intentIdentifiers: [], options: [])
}()

UNUserNotificationCenter.current().setNotificationCategories([inputCatrgory, myCatrgory])

以上代码也就是定义了一种 category 的 Action 的特征,定义的 Action 在 Appdelegate 中可以做出相应的处理。并注册到 UNUserNotificationCenter 中。此时可以通过通知发送的 payload 中的字段来控制显示哪个 category。

1
2
3
4
5
6
7
{
"aps" : {
"alert" : "Test message!",
"sound" : "default",
"category": "myCatrgory"
}
}

以上的 payload 就是指定 myCatrgory 这个类型。到此一个简单的推送就完成了。

2.初始化 UNNotificationAttachment 对象

1
let attachment = UNNotificationAttachment(identifier: "identifier", url: url, options: nil)
  • identifier:是对 attachment 的唯一标示
  • url:attachment 资源的链接
  • options:attachment 一些可选项

比如 .ThumbnailClippingRectKey 表示获取一个缩略图。

3.带多媒体的推送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.title = "iOS10 推送测试"
content.body = "附件"
content.userInfo = ["icon":"1","mutable-content":1]
content.categoryIdentifier = "InputSomething"

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)

let requestIdentifier = "imageLocal"
if let imageURL = Bundle.main.url(forResource: "avatar@2x", withExtension: "png"), let attachment = try? UNNotificationAttachment(identifier: "imageAttachment", url: imageURL, options: nil) {

content.attachments = [attachment]
}

let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)

UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
if (error != nil) {
print("error: \(error.debugDescription)")
}
})
}

以上代码涉及到 UNTimeIntervalNotificationTrigger 触发器,UNNotificationRequest 通知请求,UNMutableNotificationContent 通知内容。
其中 UNTimeIntervalNotificationTrigger 是 UNNotificationTrigger 抽象类的一个子类,UNNotificationTrigger 一共有3个子类:

  • UNCalendarNotificationTrigger 按日期推送
  • UNLocationNotificationTrigger 按地点推送
  • UNTimeIntervalNotificationTrigger 按时间推送

UNMutableNotificationContent 它相当于payload 的一个对象但是比 payload 内容要丰富。值得注意的是 userInfo 属性,一个类似字典并且其中的内容完全是由开发者来自定义的,大大提高了使用的空间,此时推送可以做更多的事情。

UNNotificationRequest 将 trigger、content 包装一层发送给 UNUserNotificationCenter 来完成本地推送。

3.带多媒体的远程推送
此时就需要用到 UNNotificationServiceExtension 应用扩展,通过在 payload 中增加 mutable-content 字段来触发改扩展。

1
2
3
4
5
6
7
8
9
10
{
"aps":{
"alert":"IOS10 推送测试",
"sound":"default",
"badge":1,
"mutable-content":1,
"category":"InputSomething"
},
"image":"https://ws1.sinaimg.cn/mw690/934b5ef8gw1fapg2ssteej20oz0oz420.jpg"
}

当推送达到 app 时,会启动扩展并回调 didReceive 方法。在该方法里面可以对推送的 UNMutableNotificationContent 做出相应的修改。在 didReceive 回调方法中的 request 包含了推送的具体信息,可以通过其 userInfo 属性来解析出多媒体的 url,当然也可以去读取本地资源来显示。
从本地读取资源:

1
let imageURL = Bundle.main.url(forResource: "lufei", withExtension: "jpg")

值得注意的是这里 Bundle 指的是扩展的沙盒,不是 app 的沙盒,所以资源的路径要正确。

读取远程的资源和本地资源相比多一步下载保存的操作:

1
2
3
4
5
6
7
8
9
10
11
private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?) -> Void) {
let task = URLSession.shared.dataTask(with: url, completionHandler: {
data, res, error in

var localURL: URL? = 下载完之后保存到本地并返回本地的 url

handler(localURL)
})

task.resume()
}

得到本地的 url 之后操作其实就一样了,都是通过 url 来生成一个 UNNotificationAttachment 对象。一切都操作完之后将这个 UNMutableNotificationContent 对象返还 contentHandler(bestAttemptContent)

自定义界面

iOS10 除了显示 Title、subTitle、body 之外,通过 3D Touch 来显示界面

其中上面的黄色区域可以理解成一个 ViewController 操作,下面绿色部分就是 Title 之类的显示内容。这部分是可以隐藏的。在扩展的目录下的 info.plist 编辑一些界面相关的东西。

  • UNNotificationExtensionCategory 触发 Extension 的 category 这里需要在注册才能有效的触发 字符串类型
  • UNNotificationExtensionInitialContentSizeRatio 上图黄色区域的长宽比,float 类型
  • UNNotificationExtensionDefaultContentHidden 默认内容是否隐藏,Bool 类型

总结

在 iOS10 中的这次推送的重构,方便了开发者对推送的集成。较高程度的自定义 UI 和多媒体增加了推送的趣味性。接下来赶快去集成吧~

参考

喵神的 blog
Apple 自家的文档 UserNotification

后记

写的不准确的地方,欢迎拍砖~
demo 地址