續上篇,在簡單鬧鐘的例子上,在通知介面上顯示圖片動畫,並用通知關聯的按鈕更新通知介面。介紹 iOS 10 通知 API 的擴充套件:自定義通知顯示介面。
《iOS 10 day by day》是 shinobicontrols 公司編寫的系列部落格,介紹開發者需要了解的 iOS 10 新特性,每週更新。本系列翻譯(文集地址)已取得官方授權。目錄點此。倉薯翻譯,歡迎指正:)
Shinobicontrols 為 iOS 和 Android 開發者提供高效能、響應式的 UI 控制元件 SDK,尤其是圖表方面的控制元件。 官網 : shinobicontrols.com twitter : @shinobicontrols
我們在 Day 5 中介紹了新的 UserNotifications
框架。新框架可以統一處理本地通知和遠端推送,同時增加了一些新 API 來控制等待中和已發出的通知。
以上這些都很棒,不過蘋果還在通知方面更進一步,讓開發者能新增一個自定義的通知介面,使用者收到通知之後可以選擇檢視這個自定義介面。要實現這個功能,需要新增一個單獨的 UserNotificationsUI
框架。這個框架的 API 特別簡單,只含有一個公共的 protocol:UNNotificationContentExtension 。
工程
我們的樣例工程是在上一篇文章的鬧鐘 app 基礎上,增加了一個炫酷的自定義通知介面。通過這個介面,使用者可以不用切換到鬧鐘 app 就能直接取消通知。先來看下效果:
跟所有 Day by Day 系列文章一樣,工程原始碼放在了 Github 上。
建立 Extension
iOS 10 的許多旗艦功能都是建立在蘋果的 Extension 架構上的。前面的系列文章 Xcode 外掛 和 iMessage 外掛 都是如此。而自定義通知介面也是用同樣的方法實現的。
首先,我們要給鬧鐘 app 的工程加一個新的 target。在下面這個選擇 target 模板的介面,選擇 Notification Content
。然後隨便起個名字,我用的是 NagMeContentExtension
。
你可能會注意到,除了預設的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 陣列。
現在把工程 Build、Run 一下,我們可以看到一個比預設的通知彈框更有意思一點的介面。
管用了!現在用的是 extension 預設的 MainInterface.storyboard
介面,然後是 NotificationViewController
裡的模板程式碼在更新介面上的 label。不過這個介面還是有幾點需要改進的地方。首先,通知的內容(”Walk Dog!!”)在 extension 的介面上和 DefaultContent 區域重複出現了兩次。我們先把這個重複的去掉吧!
去掉 DefaultContent
很簡單,只需在 Info.plist
檔案裡的 NSExtensionAttributes 下面增加一個 key ,UNNotificationExtensionDefaultContentHidden
,然後值設為 YES
,就不會顯示 DefaultContent 了。
好,下面我們來寫自定義的介面吧。
自定義的通知介面
切換到 MainInterface.storyboard
,加上 UI 控制元件。加一個 label 描述提醒的事項,加一個小喇叭的圖片。加完之後,只需拖幾個 IBOutlets 出來,就大功告成啦!
收到通知的時候,我們要更新 label 上的文字,同時搖晃小喇叭的圖片——用這種粗暴的方式吸引使用者的注意力。要實現這些功能,需要在 NotificationViewController
裡進行一些修改。我們的 viewController 實現了 UNNotificationContentExtension
這個 protocol,下面用到的就是這個 protocol 中定義的方法:
1 2 3 4 |
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
中移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 自定義通知介面的例子,比如接收日程邀請。