續(xù)上篇爬立,在簡(jiǎn)單鬧鐘的例子上,在通知界面上顯示圖片動(dòng)畫(huà)万哪,并用通知關(guān)聯(lián)的按鈕更新通知界面侠驯。介紹 iOS 10 通知 API 的擴(kuò)展:自定義通知顯示界面。
《iOS 10 day by day》是 shinobicontrols 公司編寫(xiě)的系列博客奕巍,介紹開(kāi)發(fā)者需要了解的 iOS 10 新特性吟策,每周更新。本系列翻譯(文集地址)已取得官方授權(quán)的止。目錄點(diǎn)此檩坚。倉(cāng)薯翻譯,歡迎指正:)
Shinobicontrols 為 iOS 和 Android 開(kāi)發(fā)者提供高性能诅福、響應(yīng)式的 UI 控件 SDK匾委,尤其是圖表方面的控件。 官網(wǎng) : shinobicontrols.com twitter : @shinobicontrols
我們?cè)?Day 5 中介紹了新的 UserNotifications
框架氓润。新框架可以統(tǒng)一處理本地通知和遠(yuǎn)程推送赂乐,同時(shí)增加了一些新 API 來(lái)控制等待中和已發(fā)出的通知。
以上這些都很棒咖气,不過(guò)蘋(píng)果還在通知方面更進(jìn)一步挨措,讓開(kāi)發(fā)者能添加一個(gè)自定義的通知界面挖滤,用戶(hù)收到通知之后可以選擇查看這個(gè)自定義界面。要實(shí)現(xiàn)這個(gè)功能运嗜,需要添加一個(gè)單獨(dú)的 UserNotificationsUI
框架壶辜。這個(gè)框架的 API 特別簡(jiǎn)單,只含有一個(gè)公共的 protocol:UNNotificationContentExtension
担租。
工程
我們的樣例工程是在上一篇文章的鬧鐘 app 基礎(chǔ)上砸民,增加了一個(gè)炫酷的自定義通知界面。通過(guò)這個(gè)界面奋救,用戶(hù)可以不用切換到鬧鐘 app 就能直接取消通知岭参。先來(lái)看下效果:
跟所有 Day by Day 系列文章一樣,工程源碼放在了 Github 上尝艘。
創(chuàng)建 Extension
iOS 10 的許多旗艦功能都是建立在蘋(píng)果的 Extension 架構(gòu)上的演侯。前面的系列文章 Xcode 插件 和 iMessage 插件 都是如此。而自定義通知界面也是用同樣的方法實(shí)現(xiàn)的背亥。
首先秒际,我們要給鬧鐘 app 的工程加一個(gè)新的 target。在下面這個(gè)選擇 target 模板的界面狡汉,選擇 Notification Content
娄徊。然后隨便起個(gè)名字,我用的是 NagMeContentExtension
盾戴。
你可能會(huì)注意到寄锐,除了默認(rèn)的Info.plist
之外,這個(gè) extension 還包含另外兩個(gè)文件:
-
MainInterface.storyboard
: 我們把自定義通知界面的 UI 畫(huà)在這里 -
NotificationViewController.swift
: 一個(gè) UIViewController 的子類(lèi)尖啡,這就是自定義界面的 ViewController橄仆,我們通過(guò)這個(gè)類(lèi)來(lái)管理自定義的界面。
把 Extension 與通知 category 關(guān)聯(lián)起來(lái)
現(xiàn)在工程設(shè)置好了衅斩,我們需要讓系統(tǒng)知道盆顾,是哪個(gè)通知要展示這個(gè)界面。不知道你記不記得矛渴,上一篇文章講過(guò)椎扬,一個(gè) category 就是一個(gè)很簡(jiǎn)單的對(duì)象(參考 UNNotificationCategory),里面定義了你的 app 支持哪些類(lèi)型的通知具温,以及每種通知關(guān)聯(lián)了什么操作——就是用戶(hù)把通知展開(kāi)的時(shí)候蚕涤,通知下面出現(xiàn)的那些操作按鈕。
具體實(shí)現(xiàn)這一步铣猩,需要打開(kāi) extension 的 Info.plist
揖铜,展開(kāi) NSExtensionAttributes
Dictionary,把下面 UNNotificationExtensionCategory
這個(gè)鍵對(duì)應(yīng)的值改為通知 category 的名字("reminder")达皿。注意天吓,這個(gè)值既可以填一個(gè) string 贿肩,也可以填一個(gè) string 數(shù)組,如果想讓多個(gè)通知 category 共用一個(gè) extension 界面就可以填 string 數(shù)組。
現(xiàn)在把工程 Build、Run 一下踏幻,我們可以看到一個(gè)比默認(rèn)的通知彈框更有意思一點(diǎn)的界面察署。
管用了屎暇!現(xiàn)在用的是 extension 默認(rèn)的 MainInterface.storyboard
界面,然后是 NotificationViewController
里的模板代碼在更新界面上的 label。不過(guò)這個(gè)界面還是有幾點(diǎn)需要改進(jìn)的地方。首先茂嗓,通知的內(nèi)容("Walk Dog!!")在 extension 的界面上和 DefaultContent 區(qū)域重復(fù)出現(xiàn)了兩次。我們先把這個(gè)重復(fù)的去掉吧科阎!
去掉 DefaultContent
很簡(jiǎn)單述吸,只需在 Info.plist
文件里的 NSExtensionAttributes 下面增加一個(gè) key ,UNNotificationExtensionDefaultContentHidden
锣笨,然后值設(shè)為 YES
蝌矛,就不會(huì)顯示 DefaultContent 了。
好错英,下面我們來(lái)寫(xiě)自定義的界面吧朴读。
自定義的通知界面
切換到 MainInterface.storyboard
,加上 UI 控件走趋。加一個(gè) label 描述提醒的事項(xiàng),加一個(gè)小喇叭的圖片噪伊。加完之后簿煌,只需拖幾個(gè) IBOutlets 出來(lái),就大功告成啦鉴吹!
收到通知的時(shí)候姨伟,我們要更新 label 上的文本,同時(shí)搖晃小喇叭的圖片——用這種粗暴的方式吸引用戶(hù)的注意力豆励。要實(shí)現(xiàn)這些功能夺荒,需要在 NotificationViewController
里進(jìn)行一些修改。我們的 viewController 實(shí)現(xiàn)了 UNNotificationContentExtension
這個(gè) protocol良蒸,下面用到的就是這個(gè) protocol 中定義的方法:
func didReceive(_ notification: UNNotification) {
label.text = "Reminder: \(notification.request.content.body)"
speakerLabel.shake() // 具體實(shí)現(xiàn)下載源碼可以看到
}
這個(gè)方法就是收到通知之后技扼,根據(jù)通知內(nèi)容來(lái)配置通知界面的指定方法。
看起來(lái)還不錯(cuò)嫩痰,但是中間有一大段空白剿吻,看上去不大美觀(guān)。
幸運(yùn)的是串纺,要解決這個(gè)問(wèn)題只需加 Info.plist
里再加一個(gè) key UNNotificationExtensionInitialContentSizeRatio
丽旅,它定義了自定義通知界面的高寬比椰棘。這個(gè)值可能需要多試幾次來(lái)調(diào)整,對(duì)于我們目前的情況取 0.5 就比較合適了(當(dāng)寬度是 300 的時(shí)候榄笙,高度是 150)邪狞。
NotificationViewController
就是一個(gè)單純的 UIViewController 的子類(lèi),用起來(lái)跟你平常在主 app 里用普通的 viewController 是一樣的茅撞。唯一的不同點(diǎn)在于它的 userInteraction 是 disabled 的帆卓,意思是完全無(wú)法接收到用戶(hù)的點(diǎn)擊、觸摸事件乡翅。所以有部分控件是用不了的鳞疲,比如 UIScrollView、UIButton 等蠕蚜。
接受用戶(hù)操作
自定義的界面我們畫(huà)出來(lái)了尚洽,但是還有一點(diǎn)要改進(jìn):點(diǎn)擊 “Cancel” 按鈕,只會(huì)讓用戶(hù)切回到鬧鐘 app靶累,這一步有點(diǎn)多余腺毫。
在上一篇文章我們講了怎么給通知加上操作按鈕:通知出現(xiàn)時(shí)可以進(jìn)行的每一項(xiàng)操作都是一個(gè) UNNotificationAction,關(guān)聯(lián)在通知 category 上挣柬。更詳細(xì)的介紹可以參考官方文檔潮酒。
而 UNNotificationContentExtension
這個(gè) protocol 提供了另一個(gè)處理點(diǎn)擊事件的方法:didReceive(_:completionHandler:)
。我們就用這個(gè)方法邪蛔,把小喇叭的 icon 改成紅線(xiàn)劃掉的小喇叭急黎,然后把通知從 UNNotificationCenter
中移除。
func didReceive(_ response: UNNotificationResponse,
completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "cancel" {
let request = response.notification.request
let identifiers = [request.identifier]
// 移除后續(xù)的通知
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
// 移除之前的通知侧到,不在用戶(hù)的通知列表里占地方了
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
// 通知取消的視覺(jué)反饋
speakerLabel.text = "??"
speakerLabel.cancelShake()
completion(.doNotDismiss)
}
else {
completion(.dismiss)
}
}
相關(guān)的通知都移除了勃教,UI 也更新了,接下來(lái)我們需要告訴系統(tǒng)該怎么處置這個(gè)通知界面匠抗。因?yàn)槲覀兿胱層脩?hù)看到被劃掉的小喇叭故源,得到通知被取消的視覺(jué)反饋,所以要把通知留在屏幕上汞贸,因此回調(diào)里傳入 UNNotificationContentExtensionResponseOption
的一個(gè)取值 .doNotDismiss
绳军。
既然要用這個(gè)方法處理點(diǎn)擊,就得處理好每一個(gè)按鈕事件矢腻。在這個(gè)例子里门驾,我們只有一個(gè)“Cancel”按鈕。然而多柑,如果還有別的按鈕猎唁,它們的點(diǎn)擊事件也需要處理好:要么也在 extension 工程的這個(gè)方法里處理,要么回調(diào)傳
UNNotificationContentExtensionResponseOption.dismissAndForwardAction
,傳給主 app 去處理诫隅。
擴(kuò)展閱讀
UserNotificationsUI
這個(gè)框架并沒(méi)有什么驚天動(dòng)地的突破腐魂,但它能讓用戶(hù)與 app 的交互更便捷。用戶(hù)可以直接對(duì)通知進(jìn)行操作逐纬,不用再切換到發(fā)出通知的 app 了蛔屹;甚至通知界面的 UI 也能動(dòng)態(tài)改變,來(lái)更好地反饋用戶(hù)操作的結(jié)果豁生。
關(guān)于通知的其他“高級(jí)”特性兔毒,我推薦看看 WWDC 2016 的演講視頻。這場(chǎng)視頻中甸箱,演講者給出了幾個(gè)蘋(píng)果官方 app 自定義通知界面的例子育叁,比如接收日程邀請(qǐng)。
原文地址:iOS 10 Day by Day :: Day 6 :: Notification Content Extensions
原作者:Sam Burnstone @sam_burnstone
ShinobiControls 官網(wǎng):ShinobiControls.com twitter : @shinobicontrols
譯者:戴倉(cāng)薯