在今年6月14號蘋果WWDC開發者大會上,蘋果帶來了新的iOS系統——iOS 10。蘋果爲iOS 10帶來了十大項更新。蘋果高級副總裁Craig Federighi稱這次對iOS的更新是「蘋果史上最大的iOS更新」。數組
HomeKit:iOS 10新增智能家庭應用,支持一鍵場景模式,HomeKit能夠與Siri相鏈接。 服務器
蘋果電話:蘋果更新了電話功能,來電時能夠區別出騷擾電話。 微信
如下是我關於關於iOS 10中變化比較大的推送通知的學習筆記。app
讓咱們先來看看用戶推送在iOS X中的樣子,以下圖ide
上圖這是在鎖屏界面下的推送。支持擡起手機喚醒功能。函數
上圖是Banner,能夠看到這個推送更加的易讀,而且包含更多的內容。post
上圖是通知中心。從上面三種圖能夠看到,它們都長一個樣。學習
在iOS 8 中,咱們能夠給推送增長用戶操做,這樣使推送更加具備交互性,而且容許用戶去處理用戶推送更加的迅速。到了iOS 9 中,蘋果又再次增長了快速回復功能,進一步的提升了通知的響應性。開發者能夠容許用戶經過點擊推送,並用文字進行回覆。再就到了iOS 10 中,推送變得更加給力。由於在iOS X中,推送對iOS系統來講,是很重要的一部分。在平常使用中,咱們會常常和推送打交道。推送是咱們和設備進行互動很是重要的方式。動畫
在iOS X 中,你能夠按壓推送,推送就會被展開,展現出更加詳細的用戶界面。展現出來的詳細界面對用戶來講,提供了更加有用的信息。用戶能夠經過點擊下面的按鈕,來處理一些事件,而且推送的詳細界面也會跟着用戶的操做進行更新UI界面。ui
iOS 8 中iMessage支持了快速回復功能,可是你只能看見一條信息,而且你也只能回覆一條信息。可是在iOS X中,你能夠展開推送,這個時候你就能夠看到整個對話的內容了。你能夠等待你的朋友回覆,你再回復他,而且能夠回覆不少條。
以上就是iOS X的強大功能。以上的全部功能都能經過iOS X的新API來實現。全部的新特性都能在咱們開發者開發的app裏面有所體現。
若是常用iMessage的朋友們,就會常常收到一些信息,附帶了一些照片或者視頻,因此推送中能附帶這些多媒體是很是重要的。若是推送中包含了這些多媒體信息,可使用戶不用打開app,不用下載就能夠快速瀏覽到內容。衆所周知,推送通知中帶了push payload,及時去年蘋果已經把payload的size提高到了4k bites,可是這麼小的容量也沒法使用戶能發送一張高清的圖片,甚至把這張圖的縮略圖包含在推送通知裏面,也不必定放的下去。在iOS X中,咱們可使用新特性來解決這個問題。咱們能夠經過新的service extensions來解決這個問題。
爲了能去下載service extension 裏面的attachment,咱們必須去按照以下的要求去設置你的推送通知,使你的推送通知是動態可變的。
{
aps: {
alert : {……}
mutable-content : 1
}
my-attachment : https://example.com/phtos.jpg"
}複製代碼
在上面代碼中,能夠看到加載了一個mutable-content 的flag,而後咱們就能夠引用一個連接,把你想加入到推送裏面的attachments加入到裏面來。在上面的例子裏面,咱們就加入了一個URL。更復雜的,你甚至能夠去加入一個identifier來標示你想加入到推送裏面的內容,這個identifier是你app知道的,app能經過拿到identifier,而後知道去你本身的服務器哪裏去下載內容。
經過設置完上述的部分,推送就被推送到了每一個設備的Service Extension那裏了。在每一個設備裏面的Service Extension裏面,就能夠下載任意想要的attachment了。而後推送就會帶着下載好的attachment推送到手機並顯示出來了。
若是來設置Service Extension呢?來看看以下的代碼:
// Adding an attachment to a user notification
public class NotificationService: UNNotificationServiceExtension {
override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: (UNNotificationContent) -> Void)
{
let fileURL = // ...
let attachment = UNNotificationAttachment(identifier: "image",
url: fileURL,
options: nil)
let content = request.content.mutableCopy as! UNMutableNotificationContent
content.attachments = [ attachment ]
contentHandler(content)
}
}複製代碼
首先定義了一個didReceive的方法,用來接收request,後面跟着withContentHandler的回調函數。 這個NotificationServiceExtension會在收到推送以後,被調用,而後在這個方法裏面去下載本身的attachment。下載能夠經過URL,或者任何你喜歡的方式。當下載完成以後,就能夠建立attachment對象了。建立完UNMutableNotificationContent,咱們就能夠把這個加入到推送的content中了。最後,經過contentHandler回調,把它傳遞給iOS系統,iOS 系統就會展現給用戶。
經過以上的設置,咱們就能在推送中看到豐富的媒體信息了。用戶並不須要去打開app,也不用去點擊下載。
簡單的概述一下Media Attachments:
經過以上能夠看出,Media Attachments很是的酷,它爲咱們提供了更加豐富的推送內容。
接下來咱們再來看看如何自定義推送的用戶界面
要想建立一個自定義的用戶界面,須要用到Notification content extension。
先來講說下面這個例子的應用場景:
好比有個朋友在日曆中給我了一個聚會的邀請,這個時候就來了推送,推送裏面的內容就是包含了聚會的時間地點信息,推送下面有三個按鈕,接受,謝絕。下面的例子都以此爲例。
Notification content extension容許開發者加入自定義的界面,在這個界面裏面,你能夠繪製任何你想要的東西。可是有一個最重要的限制就是,這個自定義的界面沒有交互。它們不能接受點擊事件,用戶並不能點擊它們。可是推送通知仍是能夠繼續與用戶進行交互,由於用戶可使用notificaiton的actions。extension能夠處理這些actions。
接下來咱們就來講說如何自定義界面
先來看一個日曆的推送例子:
上圖,整個推送分4段。用戶能夠經過點擊Header裏面的icon來打開app,點擊取消來取消顯示推送。Header的UI是系統提供的一套標準的UI。這套UI會提供給全部的推送通知。
Header下面是自定義內容,這裏就是顯示的Notification content extension。在這裏,就能夠顯示任何你想繪製的內容了。你能夠展現任何額外的有用的信息給用戶。
content extension下面就是default content。這裏是系統的界面。這裏的系統界面就是上面推送裏面payload裏面附帶的內容。這也就是iOS 9 以前的推送的樣子。
最下面一段就是notification action了。在這一段,用戶能夠觸發一些操做。而且這些操做還會相應的反映到上面的自定義的推送界面content extension中。
接下來咱們就來看看如何建立一個Notification content extension
第一件事就是去建立一個新的target。建立好了以後,Xcode會自動幫咱們生成一個template。template會在新的target裏面生成3個文件,一個新的ViewController,main Interface storyboard,info.plist。info.plist中就是能夠定義化一些target的配置。
打開Notification content extension的ViewController
// Minimal Content Extension
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet var label: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
// Do any required interface initialization here.
}
func didReceive(_ notification: UNNotification) {
label?.text = notification.request.content.body
}
}複製代碼
咱們會發現,這個ViewController是UIViewController的子類,其實就是一個很普通的ViewController,和咱們平時使用的沒有啥兩樣。後面是UNNotificationContentExtension的protocol,這裏是系統要求你必須實現的協議。
UNNotificationContentExtension只有一個required的方法,就是didReceive方法。當推送到達你的設備以後,這個didReceive方法會隨着ViewController的生命週期的方法 ,一塊兒被調用。當開發者給推送加上expands的時候,一旦推送送達之後,這時會接到全部的ViewController生命週期的方法,和didReceive方法。這樣,咱們就能夠接收notification object ,接着更新UI。
接下來,咱們須要作的是,告訴iOS系統,推送送達以後,iOS系統如何找到你自定義的Notification content extension。
Notification content extension和咱們註冊notification actions同樣,註冊的相同的category。這個例子中,咱們使用event-invite。值得提到的一點是,這裏的extension是能夠爲一個數組的,裏面能夠爲多個category,這樣作的目的是多個category共用同一套UI。
上圖中,event-invite 和 event-update就共用了一套UI。這樣咱們就能夠把他們打包到一個extension裏面來。可是不一樣的category是獨立的,他們能夠相應不一樣的actions。
經過以上設置,iOS系統就知道了咱們的target了。
接下來咱們來自定義UI界面。
// Notification Content Extension
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet var eventTitle: UILabel!
@IBOutlet var eventDate: UILabel!
@IBOutlet var eventLocation: UILabel!
@IBOutlet var eventMessage: UILabel!
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
eventTitle.text = content.title
eventDate.text = content.subtitle
eventMessage.text = content.body
if let location = content.userInfo["location"] as? String {
eventLocation.text = location
}
}
}複製代碼
上述代碼中,咱們在stroyboard 裏面加入了一些labels 。當接收到推送的時候,咱們提取出內容,獲得咱們想要的內容,而後把這些內容設置到label上面去,並展現出來。在content的userinfo裏面咱們還能加入一些額外的信息,這些信息是標準的payload沒法展現的,好比說位置信息等等。
代碼完成以後就是如上的樣子,中間就是咱們自定義的UIView了。可是這樣子會有2個問題。第一個問題就是這個自定義的View實在太大了。大量的空白不須要顯示出來。第二個問題就是咱們自定義的內容和下面默認的推送內容重複了。咱們須要去掉一份。
咱們先來改進上面說的第二個問題。 這個問題很簡單,其實就是一個plist的設置。咱們能夠在plist裏面把默認的content隱藏。設置以下圖。
再來講說第一個問題,界面大小的問題。 咱們能夠經過平時咱們Resize其餘ViewController同樣,來Resize這個ViewController。來看看以下的代碼。
// Notification Content Extension
class NotificationViewController: UIViewController, UNNotificationContentExtension {
override func viewDidLoad() {
super.viewDidLoad()
let size = view.bounds.size
preferredContentSize = CGSize(width: size.width, height: size.width / 2)
}
func didReceive(_ notification: UNNotification) {
// ...
}
}複製代碼
這裏咱們也能夠加入constraints來作autolayout。
解決完上面2個問題,界面就會變成這個樣子。看上去比以前好不少了。正常的尺寸,沒有多餘的空白。沒有重複信息。可是這又出現了另一個問題。當通知展現出來以後,它的大小並非正常的咱們想要的尺寸。iOS系統會去作一個動畫來Resize它的大小。以下圖,系統會先展示出第一張圖,而後緊接着展現第二張圖,這個用戶體驗不好。
會出現上面這張圖的緣由是,在推送送達的那一刻,iOS系統須要知道咱們推送界面的最終大小。可是咱們自定義的extension在系統打算展現推送通知的那一刻,並尚未啓動。因此這個時候,在咱們代碼都尚未跑起來以前,咱們須要告訴iOS系統,咱們的View最終要展現的大小。
如今問題又來了。這些通知會跑在不一樣的設備上,不一樣的設備的屏幕尺寸不一樣。爲了解決這個問題,咱們須要設置一個content size ratio。
這個屬性定義了寬和高的比例。固然設置了這個比例之後,也並非萬能的。由於你並不知道你會接受到多長的content。當你僅僅只設置比例,仍是不能完整的展現全部的內容。有些時候若是咱們能夠知道最終的尺寸,那麼咱們固定尺寸會更好。
咱們能夠給這個extension加上Media Attachments。一旦咱們加入Media Attachments,咱們能夠在content extension裏面使用這些內容。
// Notification Content Extension Attachments
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet var eventImage: UIImageView!
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
if let attachment = content.attachments.first {
if attachment.url.startAccessingSecurityScopedResource() {
eventImage.image = UIImage(contentsOfFile: attachment.url.path!)
attachment.url.stopAccessingSecurityScopedResource()
}
}
}
}複製代碼
咱們能夠提取content的attachments。前文提到過,attachment是由系統管理的,系統會把它們單獨的管理,這意味着它們存儲在咱們sandbox以外。因此這裏咱們要使用attachment以前,咱們須要告訴iOS系統,咱們須要使用它,而且在使用完畢以後告訴系統咱們使用完畢了。對應上述代碼就是startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource()的操做。當咱們獲取到了attachment的使用權以後,咱們就可使用那個文件獲取咱們想要的信息了。
上述例子中,咱們從attachment中獲取到圖片,並展現到UIImageView中。因而notification就變成下面這個樣子了。
說道這裏,咱們不得不說一下iOS8開始引入的action的工做原理: 默認系統的Action的處理是,當用戶點擊的按鈕,就把action傳遞給app,與此同時,推送通知會當即消失。這種作法很方便。
可是還有一種狀況,當用戶點擊了按鈕,但願接受一些日曆上的邀請,咱們須要把這個操做即時的展現在咱們自定義的UI上,這是咱們就只能用Notification content extension來處理這些用戶點擊事件了。這個時候,用戶點擊完按鈕,咱們把這個action直接傳遞給extension,而不是傳遞給app。當actions傳遞給extension時,它能夠延遲推送通知的消失時間。在這段延遲的時間以內,咱們就能夠處理用戶點擊按鈕的事件了,而且更新UI,一切都處理完成以後,咱們再去讓推送通知消失掉。
這裏咱們能夠運用UNNotificationContentExtension協議的第二個方法,這方法是Optional
// Intercepting notification action response
class NotificationViewController: UIViewController, UNNotificationContentExtension {
func didReceive(_ response: UNNotificationResponse, completionHandler done: (UNNotificationContentExtensionResponseOption) -> Void) {
server.postEventResponse(response.actionIdentifier) {
if response.actionIdentifier == "accept" {
eventResponse.text = "Going!"
eventResponse.textColor = UIColor.green()
} else if response.actionIdentifier == "decline" {
eventResponse.text = "Not going :("
eventResponse.textColor = UIColor.red()
}
done(.dismiss)
}
}
}複製代碼
不用這個方法的時候就能夠不聲明出來。可是一旦聲明瞭,那麼你就須要在這個方法裏面處理推送通知裏面全部的actions。這就意味着你不能只處理一個action,而無論其餘的action。
在上述代碼中,當用戶點擊了按鈕,這個時候咱們同步一下服務器信息,當接收到了服務器應答以後,而後咱們更新UI。用戶點擊了「accept」以後,表示接受了此次聚會邀請,因而咱們把text的顏色變成綠色。當用戶點擊了「decline」,表示謝絕,因而咱們把text的顏色變成紅色。當用戶點擊以後,更新完界面,咱們就讓推送通知消失掉。
這裏值得一提的是,若是你還想把這個action傳遞給app,那麼最後的參數應該是這樣。
done(.dismissAndForwardAction)複製代碼
參數設置成這樣以後,用戶的action就會再傳遞給app。
若是此時用戶還想輸入寫文字來評論這條推送,咱們該如何作?
這個輸入文字的需求是來自於iOS 9 。這個的使用方法和9是相同的。
// Text Input Action
private func makeEventExtensionCategory() -> UNNotificationCategory {
let commentAction = UNTextInputNotificationAction(
identifier: "comment",
title: "Comment",
options: [],
textInputButtonTitle: "Send",
textInputPlaceholder: "Type here...")
return UNNotificationCategory(identifier: "event-invite", actions: [ acceptAction, declineAction, commentAction ],
}複製代碼
咱們能夠建立一個UNTextInputNotificationAction,並把它設置到plist裏面的Category中。當推送通知到來以後,用戶點擊了按鈕,textfield就會顯示出來。一樣的處理action代碼以下:
// Text input action response
class NotificationViewController: UIViewController, UNNotificationContentExtension {
func didReceive(_ response: UNNotificationResponse,
completionHandler done: (UNNotificationContentExtensionResponseOption) -> Void) {
if let textResponse = response as? UNTextInputNotificationResponse {
server.send(textResponse.userText) {
}
}
}
}複製代碼
這個時候當用戶點擊了評論按鈕,就會彈出textfield。
這裏還有一個問題,就是用戶點完評論按鈕以後,以前的接受和謝絕的按鈕就消失了。這個時候用戶可能有這個需求,想又評論,又接受或者謝絕。那麼咱們就須要在下面鍵盤上加入這兩個按鈕。以下圖這樣子。
這裏並無新的API,仍是用原來的API。咱們可使用已經存在的UIKit的API去定製輸入的input accessory view。它可讓咱們開發者加入自定義的按鈕。
// Custom input accessory view
class NotificationViewController: UIViewController, UNNotificationContentExtension {
override func canBecomeFirstResponder() -> Bool {
return true
}
override var inputAccessoryView: UIView { get {
return inputView
}
}
func didReceive(_ response: UNNotificationResponse,
completionHandler done: (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "comment" {
becomeFirstResponder()
textField.becomeFirstResponder()
}
}
}複製代碼
解析一下上述的代碼。首先咱們須要讓ViewController BecomeFirstResponder。這裏作了2件事情,一是告訴responder chain,我成爲了第一響應者,二是告訴iOS系統,我不想使用系統標準的text field。接着就能夠建立自定義化的inputAccessoryView。如上圖中顯示的,帶自定義的兩個按鈕。而後,當extension接受到了用戶點擊按鈕後產生的action,這時自定義的textfield就會變成第一響應者,而且伴隨着鍵盤的彈起。
注意,這裏須要2個becomeFirstResponder,第一個becomeFirstResponder是使viewController變成第一響應者,這樣textfield就會出現。第二個becomeFirstResponder是使咱們自定義的textfield變成第一響應者,這樣鍵盤纔會彈起。
以上就是iOS X中notification的全部新特性,經過上文,咱們學到的如下的知識,總結一下:
最後,請你們多多指教。新浪微博@halfrost。