Swift – UserNotifications框架使用详解

一、UserNotifications 框架介绍

1,起源

  • 过去我们通过 UILocalNotification 来实现本地消息的推送通知(Local Notification),或者利用 APNS 进行通知消息的远程推送(Remote Notification)。如果我们程序同时用到了本地通知和远程通知,会发现它们的 API 都被随意地放在了 UIApplication 或者 UIApplicationDelegate 中,开发时代码十分混乱。
  • 到了 iOS10,苹果新增加了一个 UserNotifications.framework(用户通知框架),目的在于统一 Remote Notification(远程通知)和 Local Notification(本地通知)。过去那些杂乱的和通知相关的 API 都被统一,同时也新增了许多新功能。

2,新特性

UserNotifications 框架除了整合通知相关的 API,还增加了很多令人惊喜的特性,让我们实现许多过去没法实现的功能。

  • 更加丰富的推送内容:现在可以设置推送的 titlesubtitlebody 以及符合大小的图片、音频、视频等附件内容。
  • 更好的通知管理:过去已发出的通知不能更新。现在可以对通知进行查看、更新、删除了(哪怕是已展示通知)。
  • 更优雅的展示方式:可以设置应用在前台展示通知,自定义通知 UI

3,使用流程

UserNotifications 框架的使用大概分为以下几个过程:

  • 申请、注册通知:首先需要向用户请求通知权限,在取得权限后注册通知。
  • 创建、发送通知:然后创建一个通知并发起推送。对于远程推送 APNS 而言,还需要注册 DeviceToken
  • 展示、处理通知:在接收到推送通知后可以根据 app 的运行情况决定是否展示通知,当然也可以通过一系列的回调接口对通知进行处理加工。

二、通知权限说明

1,申请权限

(1)iOS 10 统一了推送权限的申请。不管是本地推送,还是远程推送,只需要 UNUserNotificationCenter.current().requestAuthorization() 方法申请即可。(这里我们在 AppDelegate 中申请通知权限。当然写在其它地方也是可以的。)

import UIKitimport UserNotifications @UIApplicationMainclass AppDelegateUIResponderUIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKeyAny]?) -> Bool {//请求通知权限UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {(accepted, error) inif !accepted {print("用户不允许消息通知。")}} return true} func applicationWillResignActive(_ application: UIApplication) {} func applicationDidEnterBackground(_ application: UIApplication) {} func applicationWillEnterForeground(_ application: UIApplication) {} func applicationDidBecomeActive(_ application: UIApplication) {} func applicationWillTerminate(_ application: UIApplication) {}}

(2)当第一次调用上面这个方法时,系统会弹出如下窗口询问用户是否授权。

原文:Swift - UserNotifications框架使用详解1(基本介绍,权限的申请与判断)

(3)如果用户拒绝了这个请求,再次调用该方法也不会再进行弹窗,同时也就无法收到通知。这种情况如果想要应用能接收到通知的话,只能让用户自行前往系统的设置中手动为你的应用打开通知了。因此在合适的时候弹出请求窗,并预先进行说明是很重要的。

2,判断权限

(1)在有些情况下,我们可以对推送权限设置进行检查。比如在检测到用户把通知权限关闭的时候,弹出个提示框引导用户去系统设置中打开通知权限。    比如下面代码,用户如果点击了“设置”按钮,则会自动跳转到通知设置页面,方便用户设置。    

原文:Swift - UserNotifications框架使用详解1(基本介绍,权限的申请与判断)
原文:Swift - UserNotifications框架使用详解1(基本介绍,权限的申请与判断)
原文:Swift - UserNotifications框架使用详解1(基本介绍,权限的申请与判断)
UNUserNotificationCenter.current().getNotificationSettings {settings inswitch settings.authorizationStatus {case .authorized:returncase .notDetermined://请求授权UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {(accepted, error) inif !accepted {print("用户不允许消息通知。")}}case .denied:DispatchQueue.main.async(execute: { () -> Void inlet alertController = UIAlertController(title: "消息推送已关闭",message: "想要及时获取消息。点击“设置”,开启通知。",preferredStyle: .alert) let cancelAction = UIAlertAction(title:"取消", style: .cancel, handler:nil) let settingsAction = UIAlertAction(title:"设置", style: .default, handler: {(action) -> Void inlet url = URL(string: UIApplicationOpenSettingsURLString)if let url = url, UIApplication.shared.canOpenURL(url) {if #available(iOS 10, *) {UIApplication.shared.open(url, options: [:],completionHandler: {(success) in})else {UIApplication.shared.openURL(url)}}}) alertController.addAction(cancelAction)alertController.addAction(settingsAction) self.present(alertController, animated: true, completion: nil)})}}

(2)除了打开和关闭全部通知权限外,用户也可以限制应用只能进行哪种形式的通知显示,比如:只允许横幅,而不允许声音及通知中心显示等。这些细微的设置,我们程序也是能检测到的。 

原文:Swift - UserNotifications框架使用详解1(基本介绍,权限的申请与判断)
UNUserNotificationCenter.current().getNotificationSettings {settings invar message = "是否允许通知:"switch settings.authorizationStatus {case .authorized:message.append("允许")case .notDetermined:message.append("未确定")case .denied:message.append("不允许")} message.append("\n声音:")switch settings.soundSetting{case .enabled:message.append("开启")case .disabled:message.append("关闭")case .notSupported:message.append("不支持")} message.append("\n应用图标标记:")switch settings.badgeSetting{case .enabled:message.append("开启")case .disabled:message.append("关闭")case .notSupported:message.append("不支持")} message.append("\n在锁定屏幕上显示:")switch settings.lockScreenSetting{case .enabled:message.append("开启")case .disabled:message.append("关闭")case .notSupported:message.append("不支持")} message.append("\n在历史记录中显示:")switch settings.notificationCenterSetting{case .enabled:message.append("开启")case .disabled:message.append("关闭")case .notSupported:message.append("不支持")} message.append("\n横幅显示:")switch settings.alertSetting{case .enabled:message.append("开启")case .disabled:message.append("关闭")case .notSupported:message.append("不支持")} message.append("\n显示预览:")switch settings.showPreviewsSetting{case .always:message.append("始终(默认)")case .whenAuthenticated:message.append("解锁时")case .never:message.append("从不")}  print(message)}

三、一个简单的本地通知样例

1,效果图

(1)程序启动后会自动创建并发送一个 30 秒后的通知,接着我们便可以锁屏或者将应用切到后台。(2)30 秒时间一到,如果当前是锁屏状态。通知会出现在屏幕横幅中。如果当前是在系统里的话,则会出现在屏幕顶部。当然通知中心里也会有这条通知。        

原文:Swift - UserNotifications框架使用详解2(发送本地通知)
原文:Swift - UserNotifications框架使用详解2(发送本地通知)

2,样例代码

(1)首先我们在 AppDelegate.swift 中申请通知权限。当然写在其它地方也是可以的,写这里只是为了方便测试,让程序一启动就去申请权限。

import UIKitimport UserNotifications @UIApplicationMainclass AppDelegateUIResponderUIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptionslaunchOptions: [UIApplication.LaunchOptionsKeyAny]?) -> Bool {//请求通知权限UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {(accepted, error) inif !accepted {print("用户不允许消息通知。")}} return true} func applicationWillResignActive(_ application: UIApplication) {} func applicationDidEnterBackground(_ application: UIApplication) {} func applicationWillEnterForeground(_ application: UIApplication) {} func applicationDidBecomeActive(_ application: UIApplication) {} func applicationWillTerminate(_ application: UIApplication) {}}

(2)然后在程序页面加载完毕后(ViewController.swift)创建一条简单的通知消息(30 秒后触发)。

import UIKitimport UserNotifications class ViewControllerUIViewController { override func viewDidLoad() {super.viewDidLoad() //设置推送内容let content = UNMutableNotificationContent()content.title = "hangge.com"content.body = "做最好的开发者知识平台" //设置通知触发器let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false) //设置请求标识符let requestIdentifier = "com.hangge.testNotification" //设置一个通知请求let request = UNNotificationRequest(identifier: requestIdentifier,content: content, trigger: trigger) //将通知请求添加到发送中心UNUserNotificationCenter.current().add(request) { error inif error == nil {print("Time Interval Notification scheduled: \(requestIdentifier)")}}} override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()}}

四、设置推送内容

上面的样例中我们只设置了推送通知的标题(title)和内容(body),其实还可以设置子标题(subtitle)和应用图标标记(badge)。

//设置推送内容let content = UNMutableNotificationContent()content.title = "hangge.com"content.subtitle = "航歌(二级标题)"content.body = "做最好的开发者知识平台"content.badge = 2

效果图如下:    

原文:Swift - UserNotifications框架使用详解2(发送本地通知)
原文:Swift - UserNotifications框架使用详解2(发送本地通知)

五、设置通知触发器

目前 UserNotifications 框架中一共提供了如下三种触发器。注意:触发器是只对本地通知而言的,远程推送的通知默认会在收到后立即显示。

1,一段时间后触发(UNTimeIntervalNotificationTrigger)

比如下面样例我们设置10秒钟后触发推送通知。

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

2,指定日期时间触发(UNCalendarNotificationTrigger)

(1)下面代码我们设置2017年11月11日凌晨触发推送通知。

var components = DateComponents()components.year = 2017components.month = 11components.day = 11let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)

(2)下面代码我们设置每周一上午8点都会触发推送通知。

var components = DateComponents()components.weekday = 2 //周一components.hour = 8 //上午8点components.second = 30 //30分let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)

3,根据位置触发(UNLocationNotificationTrigger)

该触发器支持进入某地触发、离开某地触发、或者两种情况均触发。下面代码设置成当手机进入到指定点(纬度:52.10,经度:51.11200 米范围内时会触发推送通知。(注意:这里我们需要 import CoreLocation 框架)

let coordinate = CLLocationCoordinate2D(latitude: 52.10, longitude: 51.11)let region = CLCircularRegion(center: coordinate, radius: 200, identifier: "center")region.notifyOnEntry = true  //进入此范围触发region.notifyOnExit = false  //离开此范围不触发let trigger = UNLocationNotificationTrigger(region: region, repeats: true)

六、远程推送基本介绍

1,什么是远程推送

  • 远程通知是指在联网的情况下,由远程服务器推送给客户端的通知,又称 APNsApple Push Notification Services)。
  • 由于在联网状态下,所有苹果设备都会与苹果服务器建立长连接。所以不管应用是打开还是关闭的情况,都能接收到服务器推送的远程通知。

2,实现原理

(1)App 打开后首先自动发送 UDID 和 BundleID 给 APNs 注册,并返回 deviceToken。(2)App 获取 deviceToken 后,调用接口将用户身份信息和 deviceToken 发送给我们的服务器,服务器将其记录下来。(3)当要推送消息时,服务器按照用户身份信息找到存储的 deviceToken,将消息和 deviToken 发送给 APNs。(4)苹果的 APNs 通过 deviceToken,找到指定设备的指定程序, 并将消息推送给用户。

3,准备工作

要开发测试远程推送功能,我们需要准备如下两个东西:

  • 真机:使用模拟器是没法注册 APNS,自然也就无法实现远程通知。
  • 推送证书:这就要求我们必须要有个苹果开发者帐号

4,证书申请

(1)首先我们需要创建应用的 APNs 证书。如果对 APNs 证书不太了解,可以请参考: iOS 证书设置指南。(2)根据指南中的“方式一”,我们创建一个推送证书(aps.cer)。将其下载到本地,并双击安装即可。

原文:Swift - UserNotifications框架使用详解3(推送远程通知)

七、远程推送样例

1,客户端准备工作

项目配置好证书后,还要打开下图的开关。

原文:Swift - UserNotifications框架使用详解3(推送远程通知)

2,客户端代码

下面是 AppDelegate.swift 的代码。我们同样是先去获得通知权限后。不过对于 APNs 而言,还需要多一个获取用户 DeviceToken 的操作(高亮部分)。

  • 实际应用中我们会把这个 DeviceToken 传递给我的的服务器,服务器后面就可以使用这个 DeviceToken 向 Apple Push Notification 的服务器提交请求,然后 APNs 通过 DeviceToken 识别设备和应用,将通知推给用户。
  • 由于获取得到的 DeviceToken 是一个 Data 类型,为了方便使用和传递,通常会将它转换为一个适合传递给 Apple 的字符串(通过 Data 扩展实现)。这里我们直接将转换后的 DeviceToken 字符串打印出来。
import UIKitimport UserNotifications @UIApplicationMainclass AppDelegateUIResponderUIApplicationDelegate { var window: UIWindow? let notificationHandler = NotificationHandler() func application(_ application: UIApplication, didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKeyAny]?) -> Bool {//请求通知权限UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {(accepted, error) inif !accepted {print("用户不允许消息通知。")}} //向APNs请求tokenUIApplication.shared.registerForRemoteNotifications() return true} //token请求回调func application(_ application: UIApplication,didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {//打印出获取到的token字符串print("Get Push token: \(deviceToken.hexString)")} func applicationWillResignActive(_ application: UIApplication) {} func applicationDidEnterBackground(_ application: UIApplication) {} func applicationWillEnterForeground(_ application: UIApplication) {} func applicationDidBecomeActive(_ application: UIApplication) {} func applicationWillTerminate(_ application: UIApplication) {}} //对Data类型进行扩展extension Data {//将Data转换为Stringvar hexString: String {return withUnsafeBytes {(bytes: UnsafePointer<UInt8>) -> String inlet buffer = UnsafeBufferPointer(start: bytes, count: count)return buffer.map {String(format: "%02hhx", $0)}.reduce("", { $0 + $1 })}}}

3,测试运行

(1)接上手机,编译运行程序。可以看到我们已经成功获取到了推送 token。特别要注意的是:

  • app 重新启动后,token 是不会变化的。
  • app 卸载重新安装的话,token 就会发生变化。
原文:Swift - UserNotifications框架使用详解3(推送远程通知)

(2)通常来说我们会有个服务端,然后通过这个 token 给对应的设备推送通知。这里为了方便测试,我们使用 APNs 调试工具 Knuff。下载地址:https://github.com/KnuffApp/Knuff/releases
(3)Knuff 使用方法如下:

  • Custom:自定义模式。我们测试自己的应用,就用这个模式,可以自行选择证书。
  • Choose:选择推送证书。也就是我们文章最开头申请的证书。
  • Sandbox:表示推送给开发版本的 App(非 AppStore 版本)。
  • Token:即我们上面注册苹果 APNs 服务时获取到的 device token
  • Payload:表示要推送的报文。
原文:Swift - UserNotifications框架使用详解3(推送远程通知)

(4)上面这些设置好以后,点击“Push”按钮即可发送远程通知。这时手机这边就可以收到这条推送消息。

原文:Swift - UserNotifications框架使用详解3(推送远程通知)

(5)上面的推送报文比较简单,通知只有一个标题(title)和应用图标标记(badge)。这次我们再增加内容(body)、子标题(subtitle)。

{
“aps”: {
“alert”: {
“title”:”hangge.com”,
“subtitle”:”航歌”,
“body”:”做最好的开发者知识平台”
},
“sound”: “default”,
“badge”: 1
}
}

手机收到通知效果如下:

原文:Swift - UserNotifications框架使用详解3(推送远程通知)

八、处理通知

UserNotifications 框架为我们提供了查找、更新、删除通知等相关的 API 方法。其中关键在于 request 的 identifier,即在创建时指定的通知标识符。

1,查找通知

(1)获取所有待推送的通知

UNUserNotificationCenter.current().getPendingNotificationRequests { (requests) in
//遍历所有未推送的
requestfor request in requests {
print(request)
}
}

目前我们只有一个未推送的通知: 

原文:Swift - UserNotifications框架使用详解4(通知的处理、回调、应用内展示)

(2)获取所有已推送的通知

UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
//遍历所有已推送的通知
for notification in notifications {
print(notification)
}
}

2,更新通知

多次推送同一标识符的通知即可进行更新。比如我们有一条标识符为“com.hangge.testNotification”的通知还未触发推送。如果创建并添加一条同样标示符的通知,那么原先的那条通知就会被替换。(而如果原先的通知已展示,则会在通知中心中更新这条通知)

//使用同样的请求标识符来设置一个新的通知
let requestIdentifier = “com.hangge.testNotification”let request = UNNotificationRequest(identifier: requestIdentifier,content: content, trigger: trigger)
//将通知请求添加到发送中心
UNUserNotificationCenter.current().add(request) { error inif error == nil {print(“Time Interval Notification scheduled: (requestIdentifier)”)}}

远程推送也可以进行通知的更新:在使用 Provider API 向 APNs 提交请求时,在 HTTP/2 的 header  apns-collapse-id key 的内容将被作为该推送的标识符进行使用。多次推送同一标识符的通知即可进行更新。

3,删除通知

(1)取消未发送的通知

//根据identifier来取消指定通知
let identifier = “com.hangge.testNotification”UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier])
//取消全部未发送通知
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()

(2)删除已发送的通知(清除通知中心里的记录)

//根据identifier来删除指定通知
let identifier = “com.hangge.testNotification”UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])
//删除全部已发送通知
UNUserNotificationCenter.current().removeAllDeliveredNotifications()

远程推送无法删除已展示的通知:现在还不能通过类似的方式,向 APNs 发送一个包含 collapse id 的 DELETE 请求来删除已经展示的推送,APNs 服务器并不接受一个 DELETE 请求。

九、应用内展示通知

默认情况下当应用处于前台时,收到的通知是不进行展示的。如果我们希望在应用内也能显示通知的话,需借助 UNUserNotificationCenterDelegate,通过该协议提供的接口方法实现应用内展示通知。

1,样例代码

我们这里在 AppDelegate 中添加相关的代理协议进行处理。

import UIKit
import UserNotifications
 
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
     
    var window: UIWindow?
     
    let notificationHandler = NotificationHandler()
     
    func application(_ application: UIApplication, didFinishLaunchingWithOptions
        launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        //请求通知权限
        UNUserNotificationCenter.current()
            .requestAuthorization(options: [.alert, .sound, .badge]) {
                (accepted, error) in
                if !accepted {
                    print(“用户不允许消息通知。”)
                }
        }
         
        //设置通知代理
        UNUserNotificationCenter.current().delegate = notificationHandler
         
        return true
    }
     
    func applicationWillResignActive(_ application: UIApplication) {
    }
     
    func applicationDidEnterBackground(_ application: UIApplication) {
    }
     
    func applicationWillEnterForeground(_ application: UIApplication) {
    }
     
    func applicationDidBecomeActive(_ application: UIApplication) {
    }
     
    func applicationWillTerminate(_ application: UIApplication) {
    }
}
 
class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
    //在应用内展示通知
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler:
        @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .sound])
         
        // 如果不想显示某个通知,可以直接用空 options 调用 completionHandler:
        // completionHandler([])
    }
}

2,效果图

可以看到当通知触发时,即使当前应用处于前台,收到的通知也仍然会进行展示。

原文:Swift - UserNotifications框架使用详解4(通知的处理、回调、应用内展示)

十、通知的响应回调

UNUserNotificationCenterDelegate 还有另外一个代理方法,会在用户与推送的通知进行交互时被调用。比如:用户通过点击通知打开了应用、点击或触发了某个 action

1,样例代码

(1)我们在程序页面加载完毕后(ViewController.swift)创建一条简单的通知消息(30 秒后触发)。特别要注意的是创建的通知出了基本的标题内容外,还附上了一些额外的信息。

import UIKitimport UserNotifications class ViewControllerUIViewController { override func viewDidLoad() {super.viewDidLoad() //设置推送内容let content = UNMutableNotificationContent()content.title = "hangge.com"content.body = "做最好的开发者知识平台"content.userInfo = ["userName""hangge""articleId": 10086] //设置通知触发器let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false) //设置请求标识符let requestIdentifier = "com.hangge.testNotification" //设置一个通知请求let request = UNNotificationRequest(identifier: requestIdentifier,content: content, trigger: trigger) //将通知请求添加到发送中心UNUserNotificationCenter.current().add(request) { error inif error == nil {print("Time Interval Notification scheduled: \(requestIdentifier)")}}} override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()}}

(2)然后在 AppDelegate 中添加相关的代理协议来处理用户与通知的交互操作。当用户点击通知后,会将该通知的标题、内容以及前面附加的额外信息给打印出来。(实际应用中我们可以根据 userInfo 的内容来决定页面跳转或者是其他后续操作)

import UIKitimport UserNotifications @UIApplicationMainclass AppDelegateUIResponderUIApplicationDelegate { var window: UIWindow? let notificationHandler = NotificationHandler() func application(_ application: UIApplication, didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKeyAny]?) -> Bool {//请求通知权限UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {(accepted, error) inif !accepted {print("用户不允许消息通知。")}} //设置通知代理UNUserNotificationCenter.current().delegate = notificationHandler return true} func applicationWillResignActive(_ application: UIApplication) {} func applicationDidEnterBackground(_ application: UIApplication) {} func applicationWillEnterForeground(_ application: UIApplication) {} func applicationDidBecomeActive(_ application: UIApplication) {} func applicationWillTerminate(_ application: UIApplication) {}} class NotificationHandlerNSObjectUNUserNotificationCenterDelegate {//对通知进行响应(用户与通知进行交互时被调用)func userNotificationCenter(_ center: UNUserNotificationCenter,didReceive response: UNNotificationResponse,withCompletionHandler completionHandler:@escaping () -> Void) {print(response.notification.request.content.title)print(response.notification.request.content.body)//获取通知附加数据let userInfo = response.notification.request.content.userInfoprint(userInfo)//完成了工作completionHandler()}}

2,效果图

(1)程序打开后退出,等待 30 秒后会收到推送通知。

原文:Swift - UserNotifications框架使用详解4(通知的处理、回调、应用内展示)

(2)点击通知则自动打开程序,同时控制台中会输出该通知的标题、内容以及附加信息。

原文:Swift - UserNotifications框架使用详解4(通知的处理、回调、应用内展示)

十一、Actionable 可交互通知

从 iOS 8 起苹果就引入了可以交互的通知,其实现方式是把一组 action 放到一个 category 中,然后将这个 category 进行注册,最后在发送通知时将通知的 category 设置为要使用的 category 即可。到了 iOS10,苹果又对这些 action 做了统一。

1,效果图 

(1)下面是一个带有 action 交互的通知。设备接收到通知后默认的展示效果和普通的通知一样,点击通知同样也会打开应用。

原文:Swift - UserNotifications框架使用详解5(Actionable可交互通知)

(2)而当我们下拉或者使用 3D touch 展开通知后,就可以看到下方会出现对应的 action 按钮。

原文:Swift - UserNotifications框架使用详解5(Actionable可交互通知)

(3)这里我们添加了三个按钮,点击后的功能分别如下:

  • 点击“点个赞”:自动打开应用,同时在程序界面上弹出用户操作信息。
  • 点击“取消”:自动清除通知,且不会打开应用。(如果我们之后手动打开程序,界面上也会弹出用户操作信息。)
  • 点击“评论”:界面下方会出现一个输入框。用户输入文字并提交后,会自动打开应用,同时在程序界面上弹出刚才输入的文字内容。
原文:Swift - UserNotifications框架使用详解5(Actionable可交互通知)
原文:Swift - UserNotifications框架使用详解5(Actionable可交互通知)
原文:Swift - UserNotifications框架使用详解5(Actionable可交互通知)

2,样例代码

(1)NotificationHandler.swift为方便管理维护,这里我们将通知响应(action 响应)放在一个单独的文件中。并且将通知的 category 和 action 的标识符都定义成枚举。

import UIKitimport UserNotifications //通知category标识符枚举enum NotificationCategoryString {case news  //新闻资讯通知category} //通知category的action标识符枚举enum NotificationCategoryActionString {case likecase cancelcase comment} //通知响应对象class NotificationHandlerNSObjectUNUserNotificationCenterDelegate {//对通知进行响应(用户与通知进行交互时被调用)func userNotificationCenter(_ center: UNUserNotificationCenter,didReceive response: UNNotificationResponse,withCompletionHandler completionHandler:@escaping () -> Void) {//根据category标识符做相应的处理let categoryIdentifier = response.notification.request.content.categoryIdentifierif let category = NotificationCategory(rawValue: categoryIdentifier) {switch category {case .news:handleNews(response: response)}}completionHandler()} //处理新闻资讯通知的交互private func handleNews(response: UNNotificationResponse) {let message: String //判断点击是那个actionif let actionType = NotificationCategoryAction(rawValue: response.actionIdentifier) {switch actionType {case .like: message = "你点击了“点个赞”按钮"case .cancel: message = "你点击了“取消”按钮"case .comment:message = "你输入的是:\((response as! UNTextInputNotificationResponse).userText)"}else {//直接点击通知,或者点击删除这个通知会进入这个分支。message = ""} //弹出相关信息if !message.isEmpty {showAlert(message: message)}} //在根视图控制器上弹出普通消息提示框private func showAlert(message: String) {if let vc = UIApplication.shared.keyWindow?.rootViewController {let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)alert.addAction(UIAlertAction(title: "确定", style: .cancel))vc.present(alert, animated: true)}}}

(2)AppDelegate.swift这里除了申请通知权限外,还注册了一个 category,里面包括两个标准按钮 action,以及一个文本输入 action

import UIKitimport UserNotifications @UIApplicationMainclass AppDelegateUIResponderUIApplicationDelegate { var window: UIWindow? let notificationHandler = NotificationHandler() func application(_ application: UIApplication, didFinishLaunchingWithOptionslaunchOptions: [UIApplicationLaunchOptionsKeyAny]?) -> Bool {//请求通知权限UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {(accepted, error) inif !accepted {print("用户不允许消息通知。")}} //注册categoryregisterNotificationCategory()UNUserNotificationCenter.current().delegate = notificationHandler return true} func applicationWillResignActive(_ application: UIApplication) {} func applicationDidEnterBackground(_ application: UIApplication) {} func applicationWillEnterForeground(_ application: UIApplication) {} func applicationDidBecomeActive(_ application: UIApplication) {} func applicationWillTerminate(_ application: UIApplication) {} //注册一个categoryprivate func registerNotificationCategory() {let newsCategory: UNNotificationCategory = {//创建输入文本的actionlet inputAction = UNTextInputNotificationAction(identifier: NotificationCategoryAction.comment.rawValue,title: "评论",options: [.foreground],textInputButtonTitle: "发送",textInputPlaceholder: "在这里留下你想说的话...") //创建普通的按钮actionlet likeAction = UNNotificationAction(identifier: NotificationCategoryAction.like.rawValue,title: "点个赞",options: [.foreground]) //创建普通的按钮actionlet cancelAction = UNNotificationAction(identifier: NotificationCategoryAction.cancel.rawValue,title: "取消",options: [.destructive]) //创建categoryreturn UNNotificationCategory(identifier: NotificationCategory.news.rawValue,actions: [inputAction, likeAction, cancelAction],intentIdentifiers: [], options: [.customDismissAction])}() //把category添加到通知中心UNUserNotificationCenter.current().setNotificationCategories([newsCategory])}}

(3)ViewController.swift 我们在程序界面打开后就创建通知(5 秒后推送)。特别注意的是要将通知的 categoryIdentifier 设置为需要的 category 标识符,这样系统就知道这个通知对应的是哪个 category

import UIKitimport UserNotifications class ViewControllerUIViewController { override func viewDidLoad() {super.viewDidLoad() //设置推送内容let content = UNMutableNotificationContent()content.title = "hangge.com"content.body = "囤积iPhoneX的黄牛赔到怀疑人生?"//设置通知对应的category标识符content.categoryIdentifier = NotificationCategory.news.rawValue //设置通知触发器let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) //设置请求标识符let requestIdentifier = "com.hangge.testNotification" //设置一个通知请求let request = UNNotificationRequest(identifier: requestIdentifier,content: content, trigger: trigger) //将通知请求添加到发送中心UNUserNotificationCenter.current().add(request) { error inif error == nil {print("Time Interval Notification scheduled: \(requestIdentifier)")}}} override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()}}

源码下载

hangge_1845.zip

3,远程推送使用 category

远程推送也可以使用 category,只需要在 payload 报文中添加 category 字段,并指定预先定义的 category id 就可以了。

{"aps":{"alert":{"title":"hangge.com","body":"囤积iPhoneX的黄牛赔到怀疑人生?"},"sound":"default","badge":1,"category":"news"}}

十二、使用 Notification Service Extension 拦截并修改通知

iOS 10 中添加了两个与通知相关的 extensionService Extension 和 Content Extension。本文先介绍下前者。

1,基本介绍

  • Service Extension 目前只对远程推送的通知有效。
  • Service Extension 可以让我们有机会在收到远程推送通知后,展示之前对通知内容进行修改。

通过本机截取推送并替换内容的方式,我们可以实现端到端 (end-to-end) 的推送加密:
我们在服务器推送 payload 中加入加密过的文本,在客户端接到通知后使用预先定义或者获取过的密钥进行解密,然后立即显示。
这样一来,即使推送信道被第三方截取,其中所传递的内容也还是安全的。使用这种方式来发送密码或者敏感信息,对于一些金融业务应用和聊天应用来说,应该是必备的特性。

2,使用说明

(1)首先我们点击”File” -> “New” -> “Target…“,使用 NotificationService 的模板来创建一个 NotificationService

原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)

(2)NotificationService 的模板已经自动为我们生成了一些基本代码,这里对其稍作修改(自动给通知内容后面加上一个小尾巴)。NotificationService 里特别要注意如下两个方法:
1,didReceive
该方法中有一个等待发送的通知请求。我们通过修改这个请求中的 content 内容,然后在限制的时间内将修改后的内容通过调用 contentHandler 返还给系统,就可以显示这个修改过的通知了。

2,serviceExtensionTimeWillExpire
在一定时间内如果没有调用 contentHandler 的话,系统会调用这个方法,来告诉我们时间到了:

  • 我们可以什么都不做,这样的话系统便当作什么都没发生,简单地显示原来的通知。
  • 或许我们已经设置好了绝大部分内容,只是有很少一部分没有完成。这时我们也可以像例子中这样调用 contentHandler 来显示一个变更到一半的通知。
import UserNotifications class NotificationServiceUNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)?var bestAttemptContent: UNMutableNotificationContent? //我们可以在后台处理接收到的推送,让后传递修改后的的内容给contentHandler进行展示override func didReceive(_ request: UNNotificationRequest,withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {self.contentHandler = contentHandlerbestAttemptContent = (request.content.mutableCopy() asUNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent {//给通知内容添加个小尾巴bestAttemptContent.body = "\(bestAttemptContent.body) 【来自hangge.com】" contentHandler(bestAttemptContent)}} //如果我们获取消息后一段时间内没有调用 contentHandler 的话,系统会调用这个方法override func serviceExtensionTimeWillExpire() {//如果消息没处理好,我们也将这个没处理完毕的消息进行展示if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {contentHandler(bestAttemptContent)}}}

(3)如果需要调试这个通知扩展类,注意 Target 要选择 NotificationService,然后编译运行时选择我们的程序。

原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)

(4)同时 Service Extension 的发布版本要低于设备的版本(比如我手机是 10.3.1,那么这里可以直接设置为 10)。

原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)

(5)最后我们在远程通知的 payload 中增加一个 mutable-content 值为 1 的项来启用内容修改(这个一定要有,否则可能会拦截通知失败)。

1234567891011{"aps": {"alert": {"title""最新资讯","body""2017全国文明城市公布"},"sound""default","badge": 1,"mutable-content": 1},}

(6)可以看到客户端这边收到通知后,会自动在内容尾部增加一个段小尾巴(【来自hangge.com】

原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)

十三、为本地通知添加多媒体内容

多媒体推送是 iOS10 新增加的一个功能。我们可以在通知中嵌入图片或者视频,这极大地丰富了推送内容的可读性和趣味性。 

1,使用说明

  • 为本地通知添加多媒体内容十分简单,只需要通过本地磁盘上的文件 URL 创建一个 UNNotificationAttachment 对象,然后将这个对象放到数组中赋值给 content 的 attachments 属性就可以了。
  • attachments 虽然是一个数组,但是系统只会展示第一个 attachment 对象的内容。不过我们依然可以发送多个 attachments,然后在要展示的时候再重新安排它们的顺序,以显示最符合情景的图片或者视频。另外,我们也可能会在自定义通知展示 UI 时用到多个 attachment,这个我们下文会进行演示。
  • 系统在创建 attachement 时会根据提供的 url 后缀确定文件类型,如果没有后缀,或者后缀不正确的话,我们可以在创建时通过 UNNotificationAttachmentOptionsTypeHintKey 来指定资源类型。

2,多媒体文件的格式、尺寸限制

(1)支持的最大尺寸:

  • 图片:10MB
  • 音频:5MB
  • 视频:50MB

(2)支持的文件格式:

  • 图片:kUTTypeJPEG、kUTTypeGIF、kUTTypePNG
  • 音频:kUTTypeAudioInterchangeFileFormat、kUTTypeWaveformAudio、kUTTypeMP3、kUTTypeMPEG4Audio
  • 视频:kUTTypeMPEG、kUTTypeMPEG2Video、kUTTypeMPEG4、kUTTypeAVIMovie

3,使用样例

(1)下面代码我们给通知附带上一张图片:

import UIKitimport UserNotifications class ViewControllerUIViewController { override func viewDidLoad() {super.viewDidLoad() //设置推送内容let content = UNMutableNotificationContent()content.title = "hangge.com"content.body = "囤积iPhoneX的黄牛赔到怀疑人生?" //给通知添加图片附件if let imageURL = Bundle.main.url(forResource: "image", withExtension: "png"),let attachment = try? UNNotificationAttachment(identifier: "imageAttachment",url: imageURL, options: nil) {content.attachments = [attachment]} //设置通知触发器let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) //设置请求标识符let requestIdentifier = "com.hangge.testNotification" //设置一个通知请求let request = UNNotificationRequest(identifier: requestIdentifier,content: content, trigger: trigger) //将通知请求添加到发送中心UNUserNotificationCenter.current().add(request) { error inif error == nil {print("Time Interval Notification scheduled: \(requestIdentifier)")}}} override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()}}

(2)效果图

  • 上面代码运行后,在通知显示时,横幅或者弹窗将附带有设置的图片。
  • 使用 3D Touch pop 通知或者下拉通知显示详细内容时,图片也会被放大展示。
  • 除了图片以外,通知还支持音频以及视频。我们可以将 MP3 或者 MP4 这样的文件提供给系统,从而在通知中进行展示和播放。
原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)
原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)
原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)

4,访问已创建的 attachment 的内容

我们可以访问一个已经创建的 attachment 的内容,但是要注意权限问题。可以使用 startAccessingSecurityScopedResource 来暂时获取已创建的 attachment 的访问权限。

let content = notification.request.contentif let attachment = content.attachments.first {if attachment.url.startAccessingSecurityScopedResource() {eventImage.image = UIImage(contentsOfFile: attachment.url.path!)attachment.url.stopAccessingSecurityScopedResource()}}

十四、为远程推送添加多媒体内容

1,实现原理

对于远程推送,我们也可以显示图片等多媒体内容。不过需要通过上面介绍的 Notification Service Extension 来修改推送通知内容的技术。具体流程如下:

  • 我们在推送的 payload 中指定需要加载的图片资源地址,这个地址可以是应用 bundle 内已经存在的资源,也可以是网络的资源。
  • 客户端收到通知后,根据资源地址创建相应的 UNNotificationAttachment。由于只能使用本地资源创建 UNNotificationAttachment,所以如果多媒体还不在本地的话,我们需要先将其下载到本地。
  • 在完成 UNNotificationAttachment 创建后,我们就可以像本地通知一样,将它设置给 attachments 属性,然后调用 contentHandler 了。

2,使用样例

(1)假设我们的远程通知 payload 报文如下,其中:

  • mutable-content:表示我们会在接收到通知时需要对内容进行更改。
  • image:表示需要显示的图片的地址。
{"aps": {"alert": {"title""最新资讯","body""2017全国文明城市公布"},"sound""default","badge": 1,"mutable-content": 1},"image""https://img1.gtimg.com/ninja/2/2017/05/ninja149447456097353.jpg"}

(2)项目这边创建一个 NotificationService,作用是接收到上面这样的通知时会自动提取图片地址、下载,并生成 attachment,然后进行通知展示。

import UserNotifications class NotificationServiceUNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)?var bestAttemptContent: UNMutableNotificationContent? //我们可以在后台处理接收到的推送,让后传递修改后的的内容给contentHandler进行展示override func didReceive(_ request: UNNotificationRequest,withContentHandler contentHandler:@escaping (UNNotificationContent) -> Void) {self.contentHandler = contentHandlerbestAttemptContent = (request.content.mutableCopy() asUNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent {//将远程推送通知中的图片下载到本地,并显示if let imageURLString = bestAttemptContent.userInfo["image"asString,let URL URL(string: imageURLString) {downloadAndSave(url: URL) { localURL inif let localURL = localURL {do {let attachment = try UNNotificationAttachment(identifier: "download",url: localURL,options: nil)bestAttemptContent.attachments = [attachment]} catch {print(error)}}contentHandler(bestAttemptContent)}}}} //如果我们获取消息后一段时间内没有调用 contentHandler 的话,系统会调用这个方法override func serviceExtensionTimeWillExpire() {//如果消息没处理好,我们也将这个没处理完毕的消息进行展示if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {contentHandler(bestAttemptContent)}} //将图片下载到本地临时文件夹中private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?) -> Void) {let task = URLSession.shared.dataTask(with: url, completionHandler: {data, res, error invar localURL: URL? = nilif let data = data {//取得当前时间的时间戳let timeInterval = Date().timeIntervalSince1970let timeStamp = Int(timeInterval)//文件后缀let ext = (url.absoluteString as NSString).pathExtensionlet temporaryURL = FileManager.default.temporaryDirectorylet url = temporaryURL.appendingPathComponent("\(timeStamp)").appendingPathExtension(ext) if let _ = try? data.write(to: url) {localURL = url}}handler(localURL)})task.resume()}}

(3)具体效果如下。可以看到即使是远程通知,附带的也是网络图片,但也是可以正常显示的。    

原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)
原文:Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)

原文出自:www.hangge.com  转载请保留原文链接:https://www.hangge.com/blog/cache/detail_1852.html

Leave a Comment