續上篇,在簡單鬧鐘的例子上,在通知界面上顯示圖片動畫,並用通知關聯的按鈕更新通知界面。介紹 iOS 10 通知 API 的擴展:自定義通知顯示界面。swift
新框架能夠統一處理本地通知和遠程推送,同時增長了一些新 API 來控制等待中和已發出的通知。api
以上這些都很棒,不過蘋果還在通知方面更進一步,讓開發者能添加一個自定義的通知界面,用戶收到通知以後能夠選擇查看這個自定義界面。要實現這個功能,須要添加一個單獨的 UserNotificationsUI 框架。這個框架的 API 特別簡單,只含有一個公共的 protocol:UNNotificationContentExtension (https://developer.apple.com/reference/usernotificationsui/unnotificationcontentextension)。數組
工程架構
咱們的樣例工程是在上一篇文章的鬧鐘 app 基礎上,增長了一個炫酷的自定義通知界面。經過這個界面,用戶能夠不用切換到鬧鐘 app 就能直接取消通知。先來看下效果:app
自定義通知界面效果框架
跟全部 Day by Day 系列文章同樣,工程源碼放在了 Github 上。ide
建立 Extension動畫
iOS 10 的許多旗艦功能都是創建在蘋果的 Extension 架構上的。前面的系列文章 Xcode 插件 和 iMessage 插件 都是如此。而自定義通知界面也是用一樣的方法實現的。ui
首先,咱們要給鬧鐘 app 的工程加一個新的 target。在下面這個選擇 target 模板的界面,選擇 Notification Content。而後隨便起個名字,我用的是 NagMeContentExtension。spa
選擇 target 模板
你可能會注意到,除了默認的Info.plist以外,這個 extension 還包含另外兩個文件:
MainInterface.storyboard : 咱們把自定義通知界面的 UI 畫在這裏
NotificationViewController.swift : 一個 UIViewController 的子類,這就是自定義界面的 ViewController,咱們經過這個類來管理自定義的界面。
把 Extension 與通知 category 關聯起來
如今工程設置好了,咱們須要讓系統知道,是哪一個通知要展現這個界面。不知道你記不記得,上一篇文章講過,一個 category 就是一個很簡單的對象(參考 UNNotificationCategory),裏面定義了你的 app 支持哪些類型的通知,以及每種通知關聯了什麼操做——就是用戶把通知展開的時候,通知下面出現的那些操做按鈕。
具體實現這一步,須要打開 extension 的 Info.plist,展開 NSExtensionAttributes Dictionary,把下面 UNNotificationExtensionCategory 這個鍵對應的值改成通知 category 的名字(「reminder」)。注意,這個值既能夠填一個 string ,也能夠填一個 string 數組,若是想讓多個通知 category 共用一個 extension 界面就能夠填 string 數組。
Info.plist
如今把工程 Build、Run 一下,咱們能夠看到一個比默認的通知彈框更有意思一點的界面。
extension 的默認界面
管用了!如今用的是 extension 默認的 MainInterface.storyboard 界面,而後是 NotificationViewController 裏的模板代碼在更新界面上的 label。不過這個界面仍是有幾點須要改進的地方。首先,通知的內容(」Walk Dog!!」)在 extension 的界面上和 DefaultContent 區域重複出現了兩次。咱們先把這個重複的去掉吧!
去掉 DefaultContent
很簡單,只需在 Info.plist 文件裏的 NSExtensionAttributes 下面增長一個 key ,UNNotificationExtensionDefaultContentHidden,而後值設爲 YES,就不會顯示 DefaultContent 了。
去掉 default content 以後
好,下面咱們來寫自定義的界面吧。
自定義的通知界面
切換到 MainInterface.storyboard,加上 UI 控件。加一個 label 描述提醒的事項,加一個小喇叭的圖片。加完以後,只需拖幾個 IBOutlets 出來,就大功告成啦!
收到通知的時候,咱們要更新 label 上的文本,同時搖晃小喇叭的圖片——用這種粗暴的方式吸引用戶的注意力。要實現這些功能,須要在 NotificationViewController 裏進行一些修改。咱們的 viewController 實現了 UNNotificationContentExtension 這個 protocol,下面用到的就是這個 protocol 中定義的方法:
func didReceive(_ notification: UNNotification) {
label.text = "Reminder: \(notification.request.content.body)"
speakerLabel.shake() // 具體實現下載源碼能夠看到
}
這個方法就是收到通知以後,根據通知內容來配置通知界面的指定方法。
初步的通知界面
看起來還不錯,可是中間有一大段空白,看上去不大美觀。
幸運的是,要解決這個問題只需加 Info.plist 裏再加一個 key UNNotificationExtensionInitialContentSizeRatio,它定義了自定義通知界面的高寬比。這個值可能須要多試幾回來調整,對於咱們目前的狀況取 0.5 就比較合適了(當寬度是 300 的時候,高度是 150)。
調整高寬比以後的界面
NotificationViewController
就是一個單純的 UIViewController 的子類,用起來跟你日常在主 app 裏用普通的 viewController 是同樣的。惟一的不一樣點在於它的 userInteraction 是 disabled 的,意思是徹底沒法接收到用戶的點擊、觸摸事件。因此有部分控件是用不了的,好比 UIScrollView、UIButton 等。
接受用戶操做
自定義的界面咱們畫出來了,可是還有一點要改進:點擊 「Cancel」 按鈕,只會讓用戶切回到鬧鐘 app,這一步有點多餘。
在上一篇文章咱們講了怎麼給通知加上操做按鈕:通知出現時能夠進行的每一項操做都是一個 UNNotificationAction,關聯在通知 category 上。更詳細的介紹能夠參考官方文檔。
而 UNNotificationContentExtension 這個 protocol 提供了另外一個處理點擊事件的方法:didReceive(_:completionHandler:)。咱們就用這個方法,把小喇叭的 icon 改爲紅線劃掉的小喇叭,而後把通知從 UNNotificationCenter 中移除。
func didReceive(_ response: UNNotificationResponse,
completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "cancel" {
let request = response.notification.request
let identifiers = [request.identifier]
// 移除後續的通知
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
// 移除以前的通知,不在用戶的通知列表裏佔地方了
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
// 通知取消的視覺反饋
speakerLabel.text = "🔇"
speakerLabel.cancelShake()
completion(.doNotDismiss)
}
else {
completion(.dismiss)
}
}
相關的通知都移除了,UI 也更新了,接下來咱們須要告訴系統該怎麼處置這個通知界面。由於咱們想讓用戶看到被劃掉的小喇叭,獲得通知被取消的視覺反饋,因此要把通知留在屏幕上,所以回調裏傳入 UNNotificationContentExtensionResponseOption 的一個取值 .doNotDismiss。
取消通知
既然要用這個方法處理點擊,就得處理好每個按鈕事件。在這個例子裏,咱們只有一個「Cancel」按鈕。然而,若是還有別的按鈕,它們的點擊事件也須要處理好:要麼也在 extension 工程的這個方法裏處理,要麼回調傳
UNNotificationContentExtensionResponseOption.dismissAndForwardAction
,傳給主 app 去處理。
擴展閱讀
UserNotificationsUI 這個框架並無什麼驚天動地的突破,但它能讓用戶與 app 的交互更便捷。用戶能夠直接對通知進行操做,不用再切換到發出通知的 app 了;甚至通知界面的 UI 也能動態改變,來更好地反饋用戶操做的結果。
關於通知的其餘「高級」特性,我推薦看看 WWDC 2016 的演講視頻。這場視頻中,演講者給出了幾個蘋果官方 app 自定義通知界面的例子,好比接收日程邀請。