Swift - UserNotifications框架使用详解7(自定义通知详情视图)
iOS 10 中添加了两个与通知相关的 extension:Service Extension 和 Content Extension。前者我在上文中已经介绍过了,本文介绍下后者:可以用来自定义通知的详细页面视图的 Content Extension。
十五、创建 Notification Content Extension
1,操作步骤
(1)首先我们点击”File” -> “New” -> “Target…”,使用 NotificationContent 的模板来创建一个 NotificationContent。
(2)使用模版创建完毕后,会自动生成如下三个文件:
(3)NotificationViewController.swift
- 这个是一个实现了 UNNotificationContentExtension 的 UIViewController 子类。
- 该 extension 中有一个必须实现的方法 didReceive(_:)。当系统需要显示自定义样式的通知详情视图时,这个方法将被调用,然后我们可以在其中配置更新我们的 UI。
- 默认生成的代码如下:直接将通知内容显示在 label 中。我们先不做修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import UIKit import UserNotifications import UserNotificationsUI class NotificationViewController : UIViewController , UNNotificationContentExtension { @IBOutlet var label: UILabel ? override func viewDidLoad() { super .viewDidLoad() } func didReceive(_ notification: UNNotification ) { self .label?.text = notification.request.content.body } } |
(4)MainInterface.storyboard
- 这里定义了该 extension 对应的详情视图 UI。
- 默认生成的 UI 如下:只有一个 label 文本标签。我们先不做修改。
(5)Info.plist
- 这里指定了该 extension 的各种配置。我们可以通过 Info.plist 控制通知详细视图的尺寸,以及是否显示原始的通知。
- 要特别注意的是 UNNotificationExtensionCategory 这个 key 值,它指定这个通知样式所对应的 category 标识符。系统在接收到通知后会通过 category 标识符先查找有没有能够处理这类通知的 content extension,如果存在,那么就交给这个 extension 来进行处理。
- 默认生成的 category 标识符是 myNotificationCategory。我们先不做修改。
2,使用样例
(1)下面代码在页面加载完毕后会自动创建个 5 秒后的推送通知。注意的是我们将通知的 categoryIdentifier 设置成上面 content extension 的 category 标识符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | import UIKit import UserNotifications class ViewController : UIViewController { override func viewDidLoad() { super .viewDidLoad() //设置推送内容 let content = UNMutableNotificationContent () content.title = "hangge.com" content.body = "做最好的开发者知识平台" //设置category标识符 content.categoryIdentifier = "myNotificationCategory" //设置通知触发器 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 in if error == nil { print ( "Time Interval Notification scheduled: \(requestIdentifier)" ) } } } override func didReceiveMemoryWarning() { super .didReceiveMemoryWarning() } } |
(2)可以看到通知的详情视图已经变成我们自定义的视图了。
十六、自定义通知详情视图(并添加 UI 交互功能)
1,效果图
(1)这里我们推送一条使用自定义详情视图的通知,这个通知里其实包含了三条新闻资讯。
(2)打开通知详情时,会显示第一条资讯的标题、内容摘要、以及相关图片。下方还有三个交互按钮:
- 点击“换一条”按钮,这个通知视图会继续显示,不过内容会切换成下一条资讯。
- 点击“打开”按钮,则自动打开应用。我们可以在程序中进行下一步操作,比如打开对应的新闻详情页面。
- 点击”取消“按钮,则自动清除通知,且不会打开应用。
2,Notification Content Extension 配置
(1)MainInterface.storyboard
- 这里我们在视图上添加两个 Label 和一个 ImageView,分别用来显示新闻资讯的标题、内容摘要、以及图片。
- 同时还要设置好各个组件的约束,并在代码中做好 @IBOutlet 关联。
(2)NotificationViewController.swift
我们在这里对详情视图进行显示和更新操作。特别注意的是,虽然我们可以使用包括按钮在内的各种 UI 组件,但是系统不允许我们与这些 UI 进行交互。点击通知视图本身会直接将我们导航到应用中,因此我们需要通过 action 按钮的方式来对视图进行更新。
didReceive(_:completionHandler:)方法介绍:
它是 UNNotificationContentExtension 的一个可选方法,它会在用户选择了某个 action 时被调用。我们可以根据情况给 completionHandler 传递不同的值实现不同操作:
- 如果需要更新详情视图,可以选择传递 .doNotDismiss 来保持通知继续被显示。
- 如果没有继续显示的必要,传递 .dismissAndForwardAction 会打开应用,并把通知的 action 继续传递给应用的 UNUserNotificationCenterDelegate 中的 userNotificationCenter(:didReceive:withCompletionHandler)
- 而传递 .dismiss 则直接将这个通知清除,同时也不会打开这个应用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | import UIKit import UserNotifications import UserNotificationsUI //资讯条目 struct NewsItem { let title: String let abstract: String let url: URL } class NotificationViewController : UIViewController , UNNotificationContentExtension { //显示资讯标题 @IBOutlet var titleLabel: UILabel ! //显示资讯内容摘要 @IBOutlet weak var abstractLabel: UILabel ! //显示资讯图片 @IBOutlet weak var imageView: UIImageView ! //当前显示的资讯索引 private var index: Int = 0 //所有资讯条目 var items: [ NewsItem ] = [] override func viewDidLoad() { super .viewDidLoad() } //收到通知 func didReceive(_ notification: UNNotification ) { //处理资讯条目 let content = notification.request.content if let news = content.userInfo[ "news" ] as ? [[ String : String ]] { for i in 0..<news.count { let title = news[i][ "title" ] ?? "" let abstract = news[i][ "abstract" ] ?? "" let url = content.attachments[i].url let presentItem = NewsItem (title: title, abstract: abstract, url: url) self .items.append(presentItem) } } //显示第一条资讯 updateNews(index: 0) } //更新显示的资讯内容 private func updateNews(index: Int ) { let item = items[index] //更新标题和内容摘要 self .titleLabel!.text = item.title self .abstractLabel.text = item.abstract //更新图片 if item.url.startAccessingSecurityScopedResource() { self .imageView.image = UIImage (contentsOfFile: item.url.path) item.url.stopAccessingSecurityScopedResource() } self .index = index } //Action按钮点击响应 func didReceive(_ response: UNNotificationResponse , completionHandler completion: @escaping ( UNNotificationContentExtensionResponseOption ) -> Void ) { if response.actionIdentifier == "change" { //切换下一条资讯 let nextIndex = (index + 1) % items.count updateNews(index: nextIndex) //保持通知继续被显示 completion(.doNotDismiss) } else if response.actionIdentifier == "open" { //取消这个通知并继续传递Action completion(.dismissAndForwardAction) } else if response.actionIdentifier == "dismiss" { //直接取消这个通知 completion(.dismiss) } else { //取消这个通知并继续传递Action completion(.dismissAndForwardAction) } } } |
(3)extension 的 Info.plist
通知扩展对应的 category 标识符这里不做修改。主要修改下 UNNotificationExtensionInitialContentSizeRatio 这个 key 值,它是 UI 界面默认的高宽比,将其修改成 0.8,这样在界面出来的时候不会有很突兀的 frame 改变。
3,Notification Content Extension 使用
(1)AppDelegate.swift
这里要注意的是我们注册一个通知 category,里面包含三个 Action 按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | import UIKit import UserNotifications @UIApplicationMain class AppDelegate : UIResponder , UIApplicationDelegate { var window: UIWindow ? func application(_ application: UIApplication , didFinishLaunchingWithOptions launchOptions: [ UIApplicationLaunchOptionsKey : Any ]?) -> Bool { UNUserNotificationCenter .current() .requestAuthorization(options: [.alert, .sound, .badge]) { (accepted, error) in if !accepted { print ( "用户不允许消息通知。" ) } } //注册category registerNotificationCategory() return true } func applicationWillResignActive(_ application: UIApplication ) { } func applicationDidEnterBackground(_ application: UIApplication ) { } func applicationWillEnterForeground(_ application: UIApplication ) { } func applicationDidBecomeActive(_ application: UIApplication ) { } func applicationWillTerminate(_ application: UIApplication ) { } //注册一个category private func registerNotificationCategory() { let newsCategory: UNNotificationCategory = { //创建三个普通的按钮action let changeAction = UNNotificationAction ( identifier: "change" , title: "换一条" , options: []) let openAction = UNNotificationAction ( identifier: "open" , title: "打开" , options: [.foreground]) //创建普通的按钮action let cancelAction = UNNotificationAction ( identifier: "cancel" , title: "取消" , options: [.destructive]) //创建category return UNNotificationCategory (identifier: "myNotificationCategory" , actions: [changeAction, openAction, cancelAction], intentIdentifiers: [], options: []) }() //把category添加到通知中心 UNUserNotificationCenter .current().setNotificationCategories([newsCategory]) } } |
(2)ViewController.swift
我们同样是页面打开后就推送个 5 秒后的通知。注意的是这里会给通知添加附加信息(包含资讯标题和内容摘要),以及资讯使用的图片附件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | import UIKit import UserNotifications class ViewController : UIViewController { override func viewDidLoad() { super .viewDidLoad() //设置推送内容 let content = UNMutableNotificationContent () content.body = "今日资讯精选【2017-12-12】" //设置通知category标识符 content.categoryIdentifier = "myNotificationCategory" //设置通知附件图片 let imageNames = [ "image1" , "image2" , "image3" ] let attachments = imageNames.flatMap { name -> UNNotificationAttachment ? in if let imageURL = Bundle .main.url(forResource: name, withExtension: "png" ) { return try? UNNotificationAttachment (identifier: "\(name)" , url: imageURL, options: nil ) } return nil } content.attachments = attachments //设置通知附加信息(资讯标题和内容摘要) content.userInfo = [ "news" : [ [ "title" : "全国人民喜迎油价上涨" , "abstract" : "据国内多家测评机构的分析,国内成品油零售限价将迎来“两连涨”..." ], [ "title" : "房价同比下降城市大幅扩容" , "abstract" : "70个大中城市中一二三线城市房价同比涨幅继续回落。这意味着,往年..." ], [ "title" : "比特币市值再创新高" , "abstract" : "一项名为SegWit2X的技术取消升级,导致在本周一比特币市值蒸发多达380亿美元..." ] ]] //设置通知触发器 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 in if error == nil { print ( "Time Interval Notification scheduled: \(requestIdentifier)" ) } } } override func didReceiveMemoryWarning() { super .didReceiveMemoryWarning() } } |
源码下载:hangge_1845.zip