一、新功能:
- Media attachments:可以添加音频,视频和图片了
- Notification Content extensions:可以自定义通知界面了
- Managing notifications:用户可以在通知界面管理通知了
- Notification Service app extensions:预处理远程通知的payloads
二、本地通知
框架的核心是UNUserNotificationCenter
,通过单例获取。
步骤:
import UserNotifications
- 在
viewDidLoad
请求授权
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound]) {
(granted, error) in
if granted {
self.loadNotificationData()
} else {
print(error?.localizedDescription)
}
}
- 在
scheduleRandomNotification
设定通知
// 1
let content = UNMutableNotificationContent()
content.title = "New cuddlePix!"
content.subtitle = "What a treat"
content.body = "Cheer yourself up with a hug 🤗 "
//TODO: Add attachment
// 2
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: seconds, repeats: false)
// 3
let request = UNNotificationRequest(
identifier: randomImageName, content: content, trigger: trigger)
// 4
UNUserNotificationCenter.current().add(request, withCompletionHandler:
{ (error) in
if let error = error {
print(error)
completion(false)
} else {
completion(true)
}
})
- 运行,进入background
三、添加附件
let attachment = try! UNNotificationAttachment(identifier:
randomImageName, url: imageURL, options: .none)
content.attachments = [attachment]
运行,进入background,用3D touch 或 下拉查看大图
四、前台通知
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.alert)
}
}
在AppDelegate
的application(_:didFinishLaunchingWithOptions:)
UNUserNotificationCenter.current().delegate = self
运行,不进入background
五、查询通知
在loadNotificationData
中:
// 1
let notificationCenter = UNUserNotificationCenter.current()
let dataSaveQueue = DispatchQueue(label:
"com.raywenderlich.CuddlePix.dataSave")
// 2
group.enter()
// 3
notificationCenter.getNotificationSettings { (settings) in
let settingsProvider = SettingTableSectionProvider(settings:
settings, name: "Notification Settings")
// 4
dataSaveQueue.async(execute: {
self.tableSectionProviders[.settings] = settingsProvider
group.leave()
})
}
group.enter()
notificationCenter.getPendingNotificationRequests { (requests) in
let pendingRequestsProvider =
PendingNotificationsTableSectionProvider(requests:
requests, name: "Pending Notifications")
dataSaveQueue.async(execute: {
self.tableSectionProviders[.pending] = pendingRequestsProvider
group.leave()
})
}
group.enter()
notificationCenter.getDeliveredNotifications { (notifications) in
let deliveredNotificationsProvider =
DeliveredNotificationsTableSectionProvider(notifications:
notifications, name: "Delivered Notifications")
dataSaveQueue.async(execute: {
self.tableSectionProviders[.delivered]
= deliveredNotificationsProvider
group.leave()
})
}
```
在`AppDelegate`的`userNotificationCenter(_:willPresent:withCompletionHandler:)`
NotificationCenter.default.post(name:
userNotificationReceivedNotificationName, object: .none)
* 通知的状态: Pending, Delivered, Deleted
### 六、修改,删除通知
* 修改通知:用相同的`identifier`创建新的`UNNotificationRequest`,内容不同。
* 删除通知
// 1
guard let section =
NotificationTableSection(rawValue: indexPath.section),
editingStyle == .delete && section == .pending else { return }
// 2
guard let provider = tableSectionProviders[.pending]
as? PendingNotificationsTableSectionProvider else { return }
let request = provider.requests[indexPath.row]
// 3
UNUserNotificationCenter.current()
.removePendingNotificationRequests(withIdentifiers:
[request.identifier])
loadNotificationData(callback: {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
})
### 七、 自定义通知界面 - Notification content extensions
1. 不能传递手势,可以响应actions
2. New a target,命名为ContentExtension
3. 画界面
4. 在 `didReceive(_:)` 方法:
// 1
guard let attachment = notification.request.content.attachments.first
else { return }
// 2
if attachment.url.startAccessingSecurityScopedResource() {
let imageData = try? Data.init(contentsOf: attachment.url)
if let imageData = imageData {
imageView.image = UIImage(data: imageData)
}
attachment.url.stopAccessingSecurityScopedResource()
}
5. 当通知来了,系统需要知道传递给哪一个extension - 配置Info.plist,和通知请求的content.categoryIdentifier设置相同的值 。
原理:通知触发了,系统查看是否设定通知请求的content.categoryIdentifier,如果没有,使用默认的界面。
如果设定了,启动相应的extension自定义的界面。
// 本地通知在Info.plist配置
{ UNNotificationExtensionCategory : newCuddlePix }
// 远程通知在payload配置
{“category":"newCuddlePix"}
// 通知请求的content
content.categoryIdentifier = newCuddlePix
6. 隐藏系统默认界面: 在 `content extension` 的`Info.plist`中添加
{ UNNotificationExtensionDefaultContentHidden : true }
7. Handling notification actions
* 交互性 - 通过自定义 custom action
* 主要的类:UNNotificationCategory
* 例如:邀请通知,同意或者拒绝
* 通过identifier区分,在`AppDelegate`中:
func configureUserNotifications() {
// 1
let starAction = UNNotificationAction(identifier:
"star", title: "* star my cuddle * ", options: [])
// 2
let category =
UNNotificationCategory(identifier: newCuddlePixCategoryName,
actions: [starAction],
intentIdentifiers: [],
options: [])
// 3
UNUserNotificationCenter.current()
.setNotificationCategories([category])
}
在`AppDelegate`的`application(_:didFinishLaunchingWithOptions:)`
configureUserNotifications()
* 运行,点击action,没有实现,则dismiss
* 在extension中,添加代码:
func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
// 1
if response.actionIdentifier == "star" {
// TODO Show Stars
let time = DispatchTime.now() +
DispatchTimeInterval.milliseconds(2000)
DispatchQueue.main.asyncAfter(deadline: time) {
// 2
completion(.dismissAndForwardAction)
}
}
}
* 添加框架到extension : 选中target,然后:
imageView.showStars()
* 通知中心代理(AppDelegate)中:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print("Response received for \(response.actionIdentifier)")
completionHandler()
}
* 方法执行顺序
1. userNotificationCenter(_:willPresent:withCompletionHandler:) :是否前台显示
2. didReceive(_:) : 显示自定义界面
3. didReceive(_:completionHandler:) : extension 处理 action
4. userNotificationCenter(_:didReceive:withCompletionHandler:) :通知中心代理处理 action
### 八、 远程通知
原理:
![APNS.png](http://upload-images.jianshu.io/upload_images/1510300-0057dc11b2e02078.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
作用: 预先处理媒体附件,解密内容
步骤:
1. 在`General`中,确定`ContentExtension` 的`bundle id` 前缀和app相同,选择Team
2. 在`Capabilities`打开 `Push Notifications`
3. 获取远程通知证书:[教程](https://www.raywenderlich.com/123862)
4. 获取 `Device Push Token`:在`AppDelegate`的`application(_:didFinishLaunchingWithOptions:)`
// 注册 - 只有在真实设备中起作用
application.registerForRemoteNotifications()
// APNS 响应
extension AppDelegate {
// 1
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Registration for remote notifications failed")
print(error.localizedDescription)
}
// 2
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("Registered with device token: (deviceToken.hexString)")
}
}
5. 下载推送软件:[Pusher](https://github.com/noodlewerk/NWPusher)
* `Pusher` 导入 `证书` 或者 `p12`文件,填写 `Device Push Token`, `payload` 填写以下内容:
{
"aps":{
"alert":{
"title":"New cuddlePix!",
"subtitle":"From your friend",
"body":"Cheer yourself up with this remote hug ) "
},
"category":"newCuddlePix"
}
}
### 九、 Notification Service app extensions
作用:预处理远程通知的payloads
1. payload添加附件
{
"aps":{
"alert":{
"title":"New cuddlePix!",
"subtitle":"From your friend",
"body":"Cheer yourself up with this remote hug ) "
},
"category":"newCuddlePix",
"mutable-content": 1
},
"attachment-url": "https://wolverine.raywenderlich.com/books/i10t/
notifications/i10t-feature.png"
}
* mutable-content : 通知是否能被 service extension 改变,默认为0
* attachment-url : 附件资源
2. 创建名为`ServiceExtension` 的target
import MobileCoreServices
在`didReceive(_:withContentHandler:)`中:
// 1
guard let attachmentString = bestAttemptContent
.userInfo["attachment-url"] as? String,
let attachmentUrl = URL(string: attachmentString) else { return }
// 2
let session = URLSession(configuration:
URLSessionConfiguration.default)
let attachmentDownloadTask = session.downloadTask(with:
attachmentUrl, completionHandler: { (url, response, error) in
if let error = error {
print("Error downloading: (error.localizedDescription)")
} else if let url = url {
// 3
let attachment = try! UNNotificationAttachment(identifier:
attachmentString, url: url, options:
[UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG])
bestAttemptContent.attachments = [attachment]
}
// 5
contentHandler(bestAttemptContent)
})
// 4
attachmentDownloadTask.resume()
3. 运行`Pusher`,推送payload