無(wú)論設(shè)備處于鎖定狀態(tài)還是使用中悯许,都可以使用通知提供及時(shí)规辱、重要的信息浴讯。無(wú)論app處于foreground、background或suspended狀態(tài)勾拉,都可以使用通知發(fā)送信息斧蜕。例如:體育類app可以使用通知告訴用戶最新比分,還可以使用通知告訴app下載數(shù)據(jù)更新界面深纲。通知的方式有顯示橫幅(banner)怀愧、播放聲音和標(biāo)記應(yīng)用程序圖標(biāo)蛤奢。
可以從應(yīng)用程序本地生成通知,也可以從服務(wù)器遠(yuǎn)程生成通知拜秧。對(duì)于本地通知痹屹,app會(huì)創(chuàng)建通知內(nèi)容,并指定觸發(fā)通知條件枉氮,如日期志衍、倒計(jì)時(shí)或位置變化。遠(yuǎn)程通知(remote notifications聊替,也稱為推送通知push notifications)需要服務(wù)器生成楼肪,由Apple Push Notification service (簡(jiǎn)稱APNs)發(fā)送到用戶設(shè)備。
iOS 10 以前通知相關(guān)API在UIApplication
或UIApplicationDelegate
中惹悄。app在前臺(tái)時(shí)春叫,遠(yuǎn)程推送無(wú)法直接顯示,需要先捕獲遠(yuǎn)程通知,然后再發(fā)起一個(gè)本地通知才能完成顯示暂殖。除此之外价匠,app運(yùn)行時(shí)和非運(yùn)行時(shí)捕獲通知的路徑不同。
iOS 10 將通知集中到UserNotifications.framework
框架呛每,絕大部分之前通知相關(guān)API都已被標(biāo)記為棄用(deprecated)踩窖。這篇文章將通過(guò)一些例子來(lái)展示iOS 10 SDK中通知相關(guān)API功能及使用方式。
1. 申請(qǐng)通知權(quán)限
通知會(huì)打擾到用戶晨横,必須先取得用戶授權(quán)洋腮。一般,可以在app啟動(dòng)時(shí)請(qǐng)求通知權(quán)限颓遏。
獲取UNUserNotificationCenter
對(duì)象徐矩,調(diào)用requestAuthorization(options:completionHandler:)
方法,指定通知所需互動(dòng)類型叁幢。如下:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Request notifications authorization.
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound, .carPlay]) { (granted, error) in
print("Permission granted:\(granted)")
// Enable or disable features based on authorization.
}
return true
}
在上面代碼中請(qǐng)求使用橫幅(alert)滤灯、徽標(biāo)(badge)、聲音曼玩、駕駛模式通知(carPlay)四種權(quán)限鳞骤。
app第一次請(qǐng)求通知權(quán)限時(shí),系統(tǒng)會(huì)彈窗提醒黍判,并記錄用戶的響應(yīng)豫尽。隨后再次申請(qǐng)通知權(quán)限時(shí),系統(tǒng)將不再提示用戶顷帖。
UNUserNotificationCenter
用于管理app和app extension通知相關(guān)任務(wù)美旧。你可以在任意線程同時(shí)調(diào)用該方法,該方法會(huì)根據(jù)任務(wù)發(fā)起時(shí)間串行執(zhí)行贬墩。
當(dāng)然榴嗅,在使用UserNotifications
相關(guān)API時(shí),需要導(dǎo)入UserNotifications
框架:
import UserNotifications
2. 本地通知
一般使用本地通知引起用戶注意陶舞。例如嗽测,后臺(tái)app可以在任務(wù)完成時(shí)顯示通知。始終使用本地通知傳達(dá)與用戶相關(guān)的重要信息肿孵。
系統(tǒng)會(huì)按照app指定的觸發(fā)條件(如時(shí)間唠粥、位置)來(lái)傳遞通知。如果發(fā)送通知時(shí)停做,app處于background或suspend晤愧,系統(tǒng)會(huì)代替app與用戶交互;如果app處于foreground雅宾,系統(tǒng)會(huì)將通知遞交至app進(jìn)行處理养涮。
2.1 創(chuàng)建通知內(nèi)容
要為本地通知指定payload葵硕,需要?jiǎng)?chuàng)建UNMutableNotificationContent
對(duì)象。使用UNMutableNotificationContent
對(duì)象為banner指定title贯吓、subtitle懈凹、body,通知聲音悄谐,以及app徽標(biāo)數(shù)值介评。
// Configure the notificaiton's payload.
let content = UNMutableNotificationContent()
content.title = "Calendar Title"
content.subtitle = "This is subtitle"
content.body = "This is body"
content.sound = UNNotificationSound.default()
content.badge = 1
通知中顯示的文本應(yīng)當(dāng)進(jìn)行本地化。盡管本地化時(shí)可以使用NSLocalizedString
宏爬舰,但更好的選擇是使用NSString
對(duì)象的localizedUserNotificationString(forKey:argumentse)
方法们陆。該方法可以在改變系統(tǒng)語(yǔ)言后,更新已顯示通知語(yǔ)言情屹。
content.title = NSString.localizedUserNotificationString(forKey: "Calendar Title", arguments: nil)
2.2 指定本地通知觸發(fā)條件
本地通知觸發(fā)條件有以下三種:
- UNCalendarNotificationTrigger
- UNTimeIntervalNotificationTrigger
- UNLocationNotificationTrigger
每個(gè)觸發(fā)條件需要不同的參數(shù)坪仇。例如,基于日歷的觸發(fā)器需要指定發(fā)送通知的日期和時(shí)間垃你。
2.2.1 UNCalendarNotificationTrigger
使用UNCalendarNotificationTrigger
對(duì)象可以在指定日期和時(shí)間觸發(fā)本地通知椅文,使用NSDateComponents
對(duì)象指定相關(guān)日期信息。系統(tǒng)會(huì)在指定日期惜颇、時(shí)間傳遞通知皆刺。
下面創(chuàng)建了一個(gè)每天早7點(diǎn)30分的提醒:
var date = DateComponents()
date.hour = 7
date.minute = 30
let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
上面的repeats
參數(shù)指定每天7點(diǎn)30分進(jìn)行提醒。
也可以根據(jù)用戶選擇的Date Picker設(shè)置trigger:
@IBAction func datePickerDidSelectNewDate(_ sender: UIDatePicker) {
let date = sender.date
let calendar = Calendar(identifier: .chinese)
let components = calendar.dateComponents(in: .current, from: date)
let newComponents = DateComponents(calendar: calendar, timeZone: .current, month: components.month, day: components.day, hour: components.hour, minute: components.minute)
let trigger = UNCalendarNotificationTrigger(dateMatching: newComponents, repeats: false)
}
關(guān)于
UNCalendarNotificationTrigger
的使用凌摄,可以參考demo中CalendarViewController.swift
部分內(nèi)容羡蛾。如果你在用Objective-C ,可以下載demo獲取Objective-C版本代碼锨亏。文章底部源碼地址包含了Swift和Objective-C兩種demo痴怨。
2.2.2 UNTimeIntervalNotificationTrigger
指定時(shí)間流逝(elapse)后觸發(fā)通知,計(jì)時(shí)器使用這種類型的觸發(fā)器器予。
下面創(chuàng)建一個(gè)2分鐘后提醒一次的觸發(fā)器:
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2*60, repeats: false)
關(guān)于
UNTimeIntervalNotificationTrigger
的使用腿箩,可以參考demo中TimeIntervalViewController.swift
部分內(nèi)容。
2.2.3 UNLocationNotificationTrigger
當(dāng)用戶設(shè)備進(jìn)入或離開(kāi)指定地理區(qū)域時(shí)觸發(fā)通知劣摇。例如,iPhone中提醒事項(xiàng)app的在指定位置提醒我功能弓乙。系統(tǒng)對(duì)所添加基于位置觸發(fā)器數(shù)量有限制末融。
使用位置觸發(fā)器前,你的app必須取得使用Core Location定位的權(quán)限暇韧。事實(shí)上勾习,系統(tǒng)負(fù)責(zé)監(jiān)控是否進(jìn)入、離開(kāi)指定地理區(qū)域懈玻。
雖然文檔要求只取得使用期間when-in-use獲取地理位置的權(quán)限即可巧婶,但經(jīng)測(cè)試和搜索,必須獲得始終 always獲取地理位置的權(quán)限。如果是我的錯(cuò)誤艺栈,特別感謝反饋指出英岭。
配置region時(shí),使用notifyOnEntry
和notifyOnExit
屬性指定進(jìn)入或離開(kāi)時(shí)觸發(fā)提醒湿右,也可以兩者都提醒诅妹。下面代碼指定離開(kāi)時(shí)進(jìn)行提醒:
// Creating a location-based trigger.
let center = CLLocationCoordinate2DMake(39.9042, 116.4074)
let region = CLCircularRegion(center: center, radius: 500, identifier: "Headquarters")
region.notifyOnExit = true
region.notifyOnEntry = false
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)
Demo中使用的是當(dāng)前設(shè)備位置盐数,具體內(nèi)容可以查看
LocationViewController.swift
部分起趾。
基于地理區(qū)域的通知并不會(huì)在設(shè)備剛剛離開(kāi)區(qū)域邊界時(shí)觸發(fā)胚膊。系統(tǒng)使用啟發(fā)式(heuristic)方法確保設(shè)備離開(kāi)指定區(qū)域退渗,而非定位有誤導(dǎo)致牢屋。
2.3 創(chuàng)建并注冊(cè)UNNotificationRequest
使用上面的UNMutableNotificationCentent
和觸發(fā)器創(chuàng)建UNNotificationRequest
對(duì)象哄孤,并通過(guò)add(_:withCompletionHandler:)
方法將通知傳遞至系統(tǒng)裆操。
// Create the request.
let request = UNNotificationRequest(identifier: "calendar", content: content, trigger: trigger)
// Schedule the request with the system.
UNUserNotificationCenter.current().add(request) { (error) in
if let error = error {
print("Failed to add request to notification center. error:\(error)")
}
}
Demo中觸發(fā)器創(chuàng)建通知如下:
通知到達(dá)時(shí)app若處于前臺(tái)蹭劈,默認(rèn)不會(huì)彈出alert缔俄。因此弛秋,需要設(shè)置通知后立即進(jìn)入后臺(tái)。
2.4 取消未展示牵现、已展示通知
通知請(qǐng)求提交后將保持活躍狀態(tài)铐懊,直到滿足其觸發(fā)條件,或顯式取消瞎疼。通常科乎,在條件變化時(shí)取消、更新通知贼急。例如茅茂,用戶提前完成了提醒,你將取消與該提醒相關(guān)的所有通知請(qǐng)求太抓;用戶更改了提醒空闲,你將更新其觸發(fā)條件。
使用UNUserNotificationCenter
調(diào)用removePendingNotificationRequests(withIdentifiers:)
方法取消通知請(qǐng)求走敌。如需更新通知碴倾,只需使用相同標(biāo)志符創(chuàng)建request即可。
- 取消還未展示的通知:使用
UNUserNotificationCenter
調(diào)用removePendingNotificationRequests(withIdentifiers:)
方法 - 更新未展示通知:使用對(duì)應(yīng)request標(biāo)志符創(chuàng)建新通知掉丽。
- 取消已展示的通知:使用
UNUserNotificationCenter
調(diào)用removeDeliveredNotifications(withIdentifiers:)
方法跌榔。 - 更新已展示通知:使用對(duì)應(yīng)request標(biāo)志符創(chuàng)建新通知。
3. 遠(yuǎn)程通知
通過(guò)支持遠(yuǎn)程通知捶障,可以向用戶提供最新信息僧须,即時(shí)app并未運(yùn)行。為了能夠接收和處理遠(yuǎn)程通知项炼,需遵守以下步驟:
- 開(kāi)啟遠(yuǎn)程通知
- 在Apple Push Notification service (APNs)注冊(cè)并接收app的device token.
- 發(fā)送device token至提供通知的服務(wù)器担平。
- 對(duì)傳入的通知進(jìn)行處理示绊。
3.1 開(kāi)啟遠(yuǎn)程通知
只有付費(fèi)的開(kāi)發(fā)者賬號(hào)才可以使用遠(yuǎn)程通知。點(diǎn)擊Project navigation中工程名稱暂论,選擇Capacities選項(xiàng)卡面褐,開(kāi)啟Push Notifications功能:
未付費(fèi)開(kāi)發(fā)者賬號(hào)不顯示 Push Notifications選項(xiàng)。
開(kāi)啟該選項(xiàng)后空另,Xcode會(huì)自動(dòng)添加entitlement盆耽,你可以登錄開(kāi)發(fā)者中心查看app IDs,會(huì)顯示應(yīng)用Push Notifications 待配置扼菠。
點(diǎn)擊底部edit按鈕摄杂,滑動(dòng)到Push Notifications部分:
在Development SSL Certificate部分,點(diǎn)擊Create Certificate按鈕循榆,根據(jù)提示創(chuàng)建CSR析恢。根據(jù)提示用上一步創(chuàng)建的CSR生成你的證書,最后下載生成的證書并導(dǎo)入到Keychain:
返回到開(kāi)發(fā)者賬號(hào)Identifiers > App IDs部分秧饮,應(yīng)用push notifications Development已經(jīng)可用:
現(xiàn)在映挂,使用Keychain證書就可以發(fā)送通知了。
3.2 在APNs注冊(cè)并接收device token
每次app啟動(dòng)盗尸,都需要在APNs注冊(cè)柑船。不同平臺(tái)注冊(cè)方法稍有不同,但其步驟是相似的:
- app請(qǐng)求在APNs注冊(cè)泼各。
- 注冊(cè)成功后鞍时,APNs發(fā)送針對(duì)該app的device token至該設(shè)備。
- 系統(tǒng)調(diào)用app中delegate方法扣蜻,并將device token傳遞給該方法逆巍。
- app將device token傳遞至服務(wù)器。
應(yīng)用程序的device token是全局唯一的莽使,標(biāo)志特定app锐极、設(shè)備的組合。收到device token后芳肌,需要由你將device token及其他相關(guān)數(shù)據(jù)(例如灵再,用戶身份信息)發(fā)送至服務(wù)器。稍后發(fā)送遠(yuǎn)程通知時(shí)亿笤,必須附帶device token和通知payload檬嘀。
不要在app內(nèi)緩存device token,每次需要時(shí)調(diào)用系統(tǒng)方法獲取责嚷。出現(xiàn)以下情況時(shí),APNs會(huì)向你的app發(fā)出新的device token:
- 用戶從備份中恢復(fù)系統(tǒng)掂铐。
- 用戶在新設(shè)備安裝了你的app罕拂。
- 用戶重新安裝了操作系統(tǒng)揍异。
當(dāng)設(shè)備令牌沒(méi)有發(fā)生變化時(shí),獲取設(shè)備令牌的方法會(huì)快速返回爆班。
當(dāng)device token變化后衷掷,用戶必須打開(kāi)你的app,以便獲取更新后的device token柿菩,APNs才可以向你的設(shè)備發(fā)送遠(yuǎn)程通知戚嗅。
watchOS設(shè)備中app不需要注冊(cè)獲取遠(yuǎn)程通知,其依靠與其配對(duì)的iPhone轉(zhuǎn)發(fā)遠(yuǎn)程通知枢舶。當(dāng)iPhone處于鎖定或屏幕處于關(guān)閉狀態(tài)懦胞,Apple Watch在用戶手腕中且未鎖定時(shí),iPhone會(huì)自動(dòng)轉(zhuǎn)發(fā)通知至Apple Watch凉泄。
在iOS和tvOS中躏尉,通過(guò)調(diào)用UIApplication
對(duì)象的registerForRemoteNotifications
方法來(lái)向APNs注冊(cè)。通常后众,在啟動(dòng)時(shí)調(diào)用此方法作為正常啟動(dòng)序列的一部分胀糜。app第一次調(diào)用此方法時(shí)會(huì)聯(lián)系A(chǔ)PNs,請(qǐng)求該app在此設(shè)備的device token蒂誉。隨后系統(tǒng)會(huì)異步調(diào)用下面兩個(gè)方法之一:
-
application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
:成功獲取device token后教藻,系統(tǒng)調(diào)用該方法。在該方法內(nèi)右锨,處理device token并轉(zhuǎn)發(fā)至服務(wù)器括堤。 -
application(_:didFailToRegisterForRemoteNotificationsWithError:)
:獲取失敗時(shí),系統(tǒng)調(diào)用該方法陡蝇∪簦可以在該方法內(nèi)禁用遠(yuǎn)程通知相關(guān)功能。
APNs的device token長(zhǎng)度可變登夫,不要硬編碼其大小广匙。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Request notifications authorization.
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound, .carPlay]) { (granted, error) in
print("Permission granted:\(granted)")
guard granted else { return }
// Register for push notification.
UIApplication.shared.registerForRemoteNotifications()
}
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Receive device token
let token = deviceToken.map { data -> String in
return String(format: "%02.2hhx", data)
}.joined()
print("Device Token:\(token)")
// Forward token to server.
// Enable remote notification features.
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Fail to register for remote notifications. error:\(error)")
// Disable remote notification features.
}
在設(shè)備中運(yùn)行,輸出如下:
Permission granted:true
Device Token:7720b86184fa24100375f2e773b9bd201130eb41aaf2a28fae1157593d1592f0
3.3 發(fā)送遠(yuǎn)程通知至APNs
遠(yuǎn)程通知從你公司服務(wù)器開(kāi)始恼策,由服務(wù)器決定何時(shí)向用戶發(fā)送通知鸦致。遠(yuǎn)程通知包含通知數(shù)據(jù)和用戶設(shè)備唯一標(biāo)志符的請(qǐng)求。將遠(yuǎn)程通知轉(zhuǎn)發(fā)給APNs涣楷,APNs負(fù)責(zé)將通知傳遞至用戶設(shè)備分唾。收到通知后,用戶設(shè)備的操作系統(tǒng)會(huì)處理用戶交互狮斗,并將通知傳遞給app绽乔。
配置服務(wù)器部分不在這篇文章范圍內(nèi),你可以查看Setting Up a Remote Notification Server這篇文檔碳褒。
使用Pusher發(fā)送遠(yuǎn)程通知
發(fā)送推送通知需要與APNs建立SSL鏈接折砸,并使用剛創(chuàng)建的證書看疗,Pusher可以完成這一任務(wù)。這篇文章中將使用Pusher向設(shè)備發(fā)送遠(yuǎn)程通知睦授,你可以根據(jù)這里的介紹安裝使用两芳。
啟動(dòng)Pusher,Pusher會(huì)自動(dòng)檢查Keychain中證書去枷,并在下拉菜單中列舉怖辆。詳細(xì)步驟如下:
從下拉菜單中選擇證書。
將控制臺(tái)輸出的device token粘貼至Device push token文本框删顶。
-
修改遠(yuǎn)程推送請(qǐng)求如下:
{ "aps":{ "alert":{ "title":"This is Title", "body":"This is Body" }, "badge":1, "sound":"default" } }
打開(kāi)運(yùn)行UserNotification Swift的設(shè)備竖螃,將app放到后臺(tái),或鎖定設(shè)備屏幕翼闹,否則會(huì)不顯示通知斑鼻。
點(diǎn)擊Pusher的Push按鈕。
現(xiàn)在猎荠,你收到了第一條遠(yuǎn)程通知坚弱。
4. Payload
每個(gè)通知都需要提供payload和device token,以及呈現(xiàn)通知的細(xì)節(jié)关摇。Payload是在服務(wù)器上創(chuàng)建的JSON字典對(duì)象荒叶。JSON字典必須包含aps
鍵,其值是通知的數(shù)據(jù)输虱。aps
鍵的內(nèi)容決定系統(tǒng)使用那種樣式呈現(xiàn)通知:
- 彈出橫幅些楣。
- 為應(yīng)用圖標(biāo)增加徽標(biāo)。
- 播放聲音宪睹。
- 以靜默方式發(fā)送通知愁茁。
在aps
字典之外,JSON詞典還可以使用自定義key亭病、value鹅很。自定義的value必須使用JSON結(jié)構(gòu),并僅使用基本類型(primitive type)罪帖,如dictionary促煮、array、string整袁、number和Boolean菠齿。請(qǐng)勿在payload中包含敏感數(shù)據(jù),除非該數(shù)據(jù)已被加密坐昙,或只對(duì)當(dāng)前應(yīng)用環(huán)境有效绳匀。例如,payload可以包含一個(gè)會(huì)話標(biāo)志符,即時(shí)消息app使用該標(biāo)志符定位相應(yīng)用戶對(duì)話襟士。通知中的數(shù)據(jù)永遠(yuǎn)不應(yīng)具有破壞性盗飒,也就是說(shuō),app不應(yīng)使用通知來(lái)刪除用戶設(shè)備上的數(shù)據(jù)陋桂。
4.1 APS詞典Keys
Apple使用aps
詞典內(nèi)鍵傳遞通知至用戶設(shè)備,aps
詞典中鍵內(nèi)容決定通知與用戶的交互方式蝶溶。下表列出了aps
詞典可用的key嗜历,除此之外的key
都會(huì)被系統(tǒng)忽略。
Key | Value type | Comment |
---|---|---|
alert |
Dictionary or String | 使用該key彈出橫幅抖所。 該鍵的首選值是字典類型梨州,該字典可選key在下一部分。如果指定字符串作為此key的值田轧,則該字符串將顯示為橫幅的消息文本暴匠。 alert中文本不支持 \U 表示法,請(qǐng)使用UTF-8傻粘。 |
badge |
Number | 使用該key修改應(yīng)用圖標(biāo)徽標(biāo)每窖。 如果沒(méi)有包含該key,則badge不變弦悉。要移除badge窒典,設(shè)置其值為 0 。 |
sound |
String | 使用該key播放聲音稽莉,其值為app main bundle或Library/Sounds目錄聲音文件名稱瀑志。如果無(wú)法找到該文件,或指定為default 污秆,系統(tǒng)播放默認(rèn)聲音劈猪。更多關(guān)于通知聲音文件信息,可以查看Preparing Custom Alert Sounds |
content-available |
Number | 包含該key良拼,并將值設(shè)置為1 战得,用于設(shè)置后臺(tái)更新通知。當(dāng)該通知到達(dá)時(shí)将饺,系統(tǒng)會(huì)喚醒你的應(yīng)用贡避,并將通知傳遞給app。更多關(guān)于background update notifications的信息予弧,查看Configuring a Background Update Notification |
category |
String | 該鍵的字符串表示通知交互類型刮吧,該鍵值的字符串對(duì)應(yīng)app注冊(cè)的category。 |
thread-id |
String | 該key用于分組通知內(nèi)容掖蛤∩蹦恚可以在Notification Content app extension中,使用該鍵的值將通知分組蚓庭。對(duì)于本地通知致讥,使用UNNotificationContent 對(duì)象的threadIdentifier 屬性仅仆。 |
mutable-content |
Number | Notification service擴(kuò)展。當(dāng)值為1 時(shí)垢袱,系統(tǒng)將通知傳遞給notification service擴(kuò)展墓拜,使用notification service擴(kuò)展修改通知內(nèi)容。 |
用戶設(shè)備中關(guān)于通知的設(shè)置最終決定是否使用alert请契、badge和sound進(jìn)行提醒咳榜。
4.2 Alert Keys
下表列出了alert
詞典接收的key,及相應(yīng)value類型爽锥。
Key | Value type | Comment |
---|---|---|
title |
String | 描述通知的簡(jiǎn)短信息涌韩。 |
body |
String | Alert通知內(nèi)容。 |
title-loc-key |
String 或 null
|
使用該key本地化Title字符串氯夷,其值在Localizable.strings 文件中臣樱。key字符串可以使用%@ 和%n$@ 格式符,以獲取title-loc-args 數(shù)組中的變量腮考。 |
title-loc-args |
元素為字符串的數(shù)組 或 null
|
替換title-loc-key 中變量雇毫。 |
5. 可操作(Actionable)通知發(fā)送和處理
Actionable notifications允許用戶直接響應(yīng)通知,而無(wú)需啟動(dòng)app秸仙。其他類型通知只顯示通知信息嘴拢,用戶唯一的操作是啟動(dòng)app。對(duì)于可操作的通知寂纪,除通知界面外席吴,系統(tǒng)還會(huì)顯示一個(gè)或多個(gè)按鈕。點(diǎn)擊按鈕會(huì)將所選操作發(fā)送到app捞蛋,然后app在后臺(tái)處理操作孝冒。
可交互式通知是通過(guò)將一簇UNNotificationAction
放到一個(gè)UNNotificationCategory
中,在app啟動(dòng)時(shí)注冊(cè)category拟杉,發(fā)送通知時(shí)將要使用category標(biāo)志符添加到payload中實(shí)現(xiàn)的庄涡。
5.1 注冊(cè)可交互式通知
UNNotificationCategory
定義app支持的交互式操作類型,UNNotificationAction
定義每種category可操作按鈕搬设。
使用init(identifier:actions:intentIdentifiers:options)
方法創(chuàng)建category穴店,其中identifier
屬性是category最重要部分,當(dāng)生成通知時(shí)拿穴,payload必須包含該標(biāo)志符泣洞。系統(tǒng)使用該identifier
屬性定位category和對(duì)應(yīng)action。
使用init(identifier:title:options:)
創(chuàng)建action默色,當(dāng)用戶點(diǎn)擊按鈕時(shí)球凰,系統(tǒng)將identifier
轉(zhuǎn)發(fā)給你的app,options
參數(shù)指定按鈕行為。例如:執(zhí)行刪除內(nèi)容操作時(shí)呕诉,使用destructive
樣式缘厢;需要啟動(dòng)app時(shí),使用foreground
樣式甩挫;只允許未鎖定設(shè)備上執(zhí)行贴硫,使用authenticationRequired
樣式。
下面為CalendarViewController
添加稍后提醒操作:
private func registerNotificationCategory() {
// calendarCategory
let completeAction = UNNotificationAction(identifier: "markAsCompleted",
title: "Mark as Completed",
options: [])
let remindMeIn1MinuteAction = UNNotificationAction(identifier: "remindMeIn1Minute",
title: "Remind me in 1 Minute",
options: [])
let remindMeIn5MinutesAction = UNNotificationAction(identifier: "remindMeIn5Minutes",
title: "Remind me in 5 Minutes",
options: [])
let calendarCategory = UNNotificationCategory(identifier: "calendarCategory",
actions: [completeAction, remindMeIn5MinutesAction, remindMeIn1MinuteAction],
intentIdentifiers: [],
options: [.customDismissAction])
UNUserNotificationCenter.current().setNotificationCategories([calendarCategory])
}
記得在application(_:didFinishLaunchingWithOptions:)
方法調(diào)用上述方法伊者。
所有action對(duì)象都必須具有唯一標(biāo)志符夜畴。處理action時(shí),標(biāo)志符是區(qū)分一個(gè)操作與另一個(gè)操作的唯一方法删壮。
5.2 payload中添加category
只有payload中包含有效category identifier,通知才會(huì)顯示action兑牡。系統(tǒng)使用payload中category identifier在已注冊(cè)category和action中查找央碟,使用查找到的信息為通知添加action按鈕。
要為本地通知添加category均函,將相應(yīng)字符串分配給UNMutableNotificationContent
對(duì)象的categoryIdentifier
屬性亿虽。
為上面創(chuàng)建的CalendarViewController
添加category:
content.categoryIdentifier = "calendarCategory"
詳細(xì)內(nèi)容,可以查看demo中
CalendarViewController.swift
部分內(nèi)容苞也。
運(yùn)行如下:
要為遠(yuǎn)程通知添加category洛勉,只需為JSON payload中aps
字典添加category
key即可。
6. 響應(yīng)通知
目前為止如迟,點(diǎn)擊通知alert只會(huì)打開(kāi)app收毫,Action中按鈕也無(wú)法點(diǎn)擊;app處于前臺(tái)時(shí)也無(wú)法收到通知殷勘。
UNUserNotificationCenterDelegate
協(xié)議中的方法用來(lái)處理與通知的交互此再,以及app處于前臺(tái)時(shí)如何響應(yīng)通知。
-
userNotification(_:didReceive:withCompletionHandler:)
: app處于后臺(tái)玲销、未運(yùn)行時(shí)输拇,系統(tǒng)會(huì)調(diào)用該方法,可以在該方法內(nèi)響應(yīng)actionable通知贤斜,以及用戶點(diǎn)擊通知時(shí)執(zhí)行的操作策吠。例如,打開(kāi)指定頁(yè)面瘩绒。 -
userNotificationCenter(_:willPresent:withCompletionHandler:)
:app處于前臺(tái)時(shí)猴抹,系統(tǒng)會(huì)調(diào)用該方法,在該方法內(nèi)決定如何處理通知草讶。
6.1 處理actionable通知
當(dāng)用戶點(diǎn)擊通知中action時(shí)洽糟,系統(tǒng)會(huì)在后臺(tái)啟動(dòng)你的app,并調(diào)用userNotification(_:didReceive:withCompletionHandler:)
方法,在該方法內(nèi)將response
的actionIdentifier
與注冊(cè)action標(biāo)志符進(jìn)行匹配坤溃。如果用戶使用系統(tǒng)默認(rèn)交互打開(kāi)app拍霜,或清除通知,系統(tǒng)會(huì)自動(dòng)匹配UNNotificationDefaultActionIdentifier
和UNNotificationDismissActionIdentifier
薪介。
下面實(shí)現(xiàn)了CalendarViewController
中actionable通知:
// Use this method to process the user's response to a notification.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
print("Default Action")
} else if (response.actionIdentifier == UNNotificationDismissActionIdentifier){
print("Dismiss action")
}else if (response.notification.request.content.categoryIdentifier == "calendarCategory") {
handleCalendarCategory(response: response)
}
UIApplication.shared.applicationIconBadgeNumber = 0
completionHandler()
}
private func handleCalendarCategory(response: UNNotificationResponse) {
if response.actionIdentifier == "markAsCompleted" {
} else if response.actionIdentifier == "remindMeIn1Minute" {
// 1 Minute
let newDate = Date(timeInterval: 60, since: Date())
scheduleNotification(at: newDate)
} else if response.actionIdentifier == "remindMeIn1Minute" {
// 5 Minutes
let newDate = Date(timeInterval: 60*5, since: Date())
scheduleNotification(at: newDate)
}
}
運(yùn)行demo祠饺,CalendarViewController
通知中的Mark as Completed、Remind me in 1 Minute和Remind me in 5 Minutes按鈕將可以使用汁政。
如果是actionable通知道偷,用戶清楚通知,或通過(guò)通知打開(kāi)app時(shí)记劈,會(huì)調(diào)用系統(tǒng)預(yù)定義的UNNotificationDismissAction
或UNNotificationDefaultActionIdentifier
勺鸦。
關(guān)于
categoryIdentifier
和actionIdentifier
的使用,可以參考demo中NotificationHandler.swift
部分內(nèi)容目木。
6.2 應(yīng)用內(nèi)展示通知
現(xiàn)在换途,app處于后臺(tái)或殺死時(shí)可以顯示通知,并響應(yīng)actionable通知刽射。但app處于前臺(tái)時(shí)军拟,收到的通知是無(wú)法顯示的。如果希望在應(yīng)用內(nèi)也顯示通知的話誓禁,需要額外工作懈息。
當(dāng)app處于前臺(tái)時(shí),通知到達(dá)時(shí)系統(tǒng)會(huì)調(diào)用UNNotificationCenterDelegate
協(xié)議的userNotificationCenter(_:willPresent:withCompletionHandler:)
方法摹恰,使用該方法處理通知辫继,并使用completionHandler()
告知系統(tǒng)所需的提醒方式。
設(shè)置Calendar通知只有app處于后臺(tái)時(shí)才顯示戒祠,其它通知直接顯示骇两。
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let identifier = notification.request.identifier
let options: UNNotificationPresentationOptions
if identifier == "calendar" {
options = []
} else {
options = [.alert, .sound]
}
completionHandler(options)
}
如果你的app注冊(cè)使用了PushKit
,PushKit
類型的通知會(huì)直接發(fā)送至app姜盈,不會(huì)直接顯示給用戶低千。如果app處于foreground或background,系統(tǒng)會(huì)為你的應(yīng)用提供處理通知的時(shí)間馏颂;如果你的應(yīng)用未運(yùn)行示血,系統(tǒng)會(huì)在后臺(tái)啟動(dòng)你的應(yīng)用,以便app可以處理通知救拉。
6.3 設(shè)置代理對(duì)象
實(shí)現(xiàn)UNUserNotificationCenterDelegate
協(xié)議方法后难审,將其分配給UNUserNotificationCenter
對(duì)象的delegate
屬性。
let notificationHandler = NotificationHandler()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
...
UNUserNotificationCenter.current().delegate = notificationHandler
return true
}
在app啟動(dòng)完畢前亿絮,必須將代理對(duì)象分配給
UNUserNotificationCenter
對(duì)象告喊。例如麸拄,在iOS應(yīng)用中,必須在applicaiton(_:willFinishLaunchingWithOptions:)
或application(_:didFinishLaunchingWithOptions:)
方法之一設(shè)置代理黔姜。在此之后設(shè)置代理拢切,可能導(dǎo)致應(yīng)用錯(cuò)過(guò)通知。
7. Notification Service Extension
有時(shí)需要在用戶iOS設(shè)備上修改遠(yuǎn)程通知內(nèi)容:
- 在服務(wù)器推送payload中加入加密文本秆吵,在客戶端接收到通知后進(jìn)行解密淮椰,以便完成端到端(end-to-end)的加密。
- 使用HTTP/2發(fā)送的payload纳寂,大小不得超過(guò)4KB主穗。可以通過(guò)在payload中添加url毙芜,顯示通知前下載附件解決這一問(wèn)題忽媒。
- 顯示通知前,使用用戶設(shè)備上數(shù)據(jù)腋粥,更新通知內(nèi)容猾浦。
想要修改遠(yuǎn)程通知內(nèi)容,需要使用notification service應(yīng)用擴(kuò)展灯抛。在用戶收到通知之前,Notification service app extension有約30秒時(shí)間來(lái)處理遠(yuǎn)程通知音瓷。
Notification service app extensions只修改遠(yuǎn)程通知的內(nèi)容对嚼。如果用戶禁用了app通知,或payload只使用sound绳慎、badge提醒用戶纵竖,或通知為silent通知,該擴(kuò)展不會(huì)響應(yīng)杏愤。
如果你還不了解擴(kuò)展靡砌,可以查看Today Extension(widget)的使用這篇文章。
7.1 為工程添加service extension
和其它擴(kuò)展一樣珊楼,notification service app extension在你的iOS應(yīng)用中處于獨(dú)立bundle通殃。添加該擴(kuò)展步驟如下:
- 選擇Xcode中的 File > New > Target...
- 選取 iOS > Application Extension 中的Notification Service Extension。點(diǎn)擊Next厕宗。
- 指定擴(kuò)展名稱和其它信息画舌。點(diǎn)擊Finish。
Xcode中Notification Service Extension模板提供了NotificationService.swift
文件和info.plist
文件已慢。在info.plist
文件中曲聂,已自動(dòng)為NSExtensionPointIdentifier
鍵設(shè)com.apple.usernotifications.service值。
7.2 實(shí)現(xiàn)擴(kuò)展的handler methods
Notification service應(yīng)用擴(kuò)展提供了以下方法用于修改遠(yuǎn)程通知內(nèi)容:
-
didReceive(_:withContentHandler:)
:該方法必須實(shí)現(xiàn)佑惠。通過(guò)重寫該方法修改遠(yuǎn)程通知的UNNotificationContent
朋腋。修改通知內(nèi)容后調(diào)用content handler塊齐疙。如果決定放棄修改通知內(nèi)容,在調(diào)用contentHandler
塊時(shí)傳入request
原始內(nèi)容旭咽。 -
serviceExtensionTimeWillExpire()
:該方法可選實(shí)現(xiàn)贞奋,但強(qiáng)烈推薦實(shí)現(xiàn)。didReceive(_:withContentHandler:)
方法占用太長(zhǎng)時(shí)間執(zhí)行任務(wù)時(shí)轻专,系統(tǒng)會(huì)在單獨(dú)線程調(diào)用該方法忆矛,以便提供最后一次修改通知的機(jī)會(huì)。此時(shí)请垛,應(yīng)當(dāng)盡快調(diào)用contentHandler
催训。例如,如果擴(kuò)展正在下載圖片宗收,你可以更新通知alert文本漫拭,提示用戶有正在下載的圖片。如果你沒(méi)有及時(shí)調(diào)用didReceive(_:withContentHandler:)
方法中的完成處理程序混稽,系統(tǒng)會(huì)顯示通知的原始內(nèi)容采驻。
進(jìn)入NotificationService.swift
文件,實(shí)現(xiàn)didReceive(_:withContentHandler:)
方法:
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
bestAttemptContent?.body = "\(bestAttemptContent?.body ?? "Default Body") pro648"
contentHandler(bestAttemptContent)
}
7.3 配置payload
當(dāng)遠(yuǎn)程通知滿足以下條件時(shí)匈勋,系統(tǒng)才會(huì)調(diào)用notification service app extension:
- Payload必須包含
mutable-content
key礼旅,且其值為1
。 - Payload的alert字典需包含title洽洁、subtitle或body這些信息痘系,即通知必須顯示banner。
遠(yuǎn)程通知payload如下:
{
"aps" : {
"alert" : {
"title" : "This is title",
"body" : "This is body",
},
"badge" : 2,
"sound" : "default",
"mutable-content" : 1,
},
}
發(fā)送通知饿自,如下所示:
使用notification service app extension汰翠,可以修改通知中任何內(nèi)容≌汛疲可以下載圖片复唤、視頻,并將其作為attachment添加至通知content烛卧。你也可以修改alert內(nèi)容佛纫,但不能移除alert內(nèi)容。如果通知content不包含alert总放,系統(tǒng)會(huì)忽略你的修改雳旅,直接原樣呈現(xiàn)。
在payload中發(fā)送圖片網(wǎng)址间聊,以附件方式顯示圖片:
{
"aps" : {
"alert" : {
"title" : "This is title",
"body" : "This is body",
},
"badge" : 2,
"sound" : "default",
"mutable-content" : 1,
},
"media-url" : "https://raw.githubusercontent.com/wiki/pro648/tips/images/UNA.jpg",
}
更新didReceive(_:withContentHandler:)
方法如下:
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
// Dig in the payload to get the attachment-url.
guard let bestAttemptContent = bestAttemptContent,
let attachmentURLAsString = request.content.userInfo["media-url"] as? String,
let attachmentURL = URL(string: attachmentURLAsString) else {
return
}
// Download the image and pass it to attachments if not nil.
downloadImageFrom(url: attachmentURL) {(attachment) in
if attachment != nil {
bestAttemptContent.attachments = [attachment!]
contentHandler(bestAttemptContent)
}
}
}
extension NotificationService {
private func downloadImageFrom(url: URL, with completionHandler: @escaping (UNNotificationAttachment?) -> Void) {
let task = URLSession.shared.downloadTask(with: url) { (location, response, error) in
// 1. Test URL and escape if URL not OK
guard let location = location else {
completionHandler(nil)
return
}
// 2. Get current's user temporary directory path
var urlPath = URL(fileURLWithPath: NSTemporaryDirectory())
// 3. Add proper ending to url path, in the case .jpg (The system validates the content of attached files before scheduling the corresponding notification request. If an attached file is corrupted, invalid, or of an unsupported file type, the notificaiton request is not scheduled for delivery.)
let uniqueURLString = ProcessInfo.processInfo.globallyUniqueString + ".png"
urlPath = urlPath.appendingPathComponent(uniqueURLString)
// 4. Move downloadUrl to newly created urlPath
try? FileManager.default.moveItem(at: location, to: urlPath)
// 5. Try adding the attachement and pass it to the completion handler
do {
let attachment = try UNNotificationAttachment(identifier: "picture", url: urlPath, options: nil)
completionHandler(attachment)
}
catch {
completionHandler(nil)
}
}
task.resume()
}
}
運(yùn)行demo攒盈,發(fā)送遠(yuǎn)程通知。如下:
擴(kuò)展修改通知內(nèi)容哎榴、調(diào)用contentHandler
方法的時(shí)間不得超過(guò)30秒型豁。如果沒(méi)有及時(shí)調(diào)用contentHandler
方法聘芜,系統(tǒng)會(huì)調(diào)用serviceExtensionTimeWillExpire()
方法宰译,這是你最后一次修改content的機(jī)會(huì)承匣。
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "Incoming Image"
contentHandler(bestAttemptContent)
}
}
8. UserNotificationsUI
當(dāng)iOS設(shè)備收到通知時(shí)留潦,系統(tǒng)分兩個(gè)階段顯示alert。
- 最初衣形,其會(huì)顯示一個(gè)標(biāo)題驼侠、副標(biāo)題,以及兩到四行正文谆吴。
- 用戶用力按壓alert倒源,或下劃alert時(shí),iOS將顯示完整通知句狼,包含響應(yīng)按鈕笋熬。
系統(tǒng)提供略縮alert界面,你可以使用UserNotificationUI
自定義完整通知界面腻菇。
添加notification content extension方法步驟和notification service extension胳螟。選擇Xcode中 File > New > Target...,選擇 iOS > Application Extension > Notification Content Extonension模板筹吐。
8.1 Info.plist文件
一個(gè)工程可以添加多個(gè)notification content service糖耸,但每一個(gè)擴(kuò)展至少支持一個(gè)單獨(dú)category。在Info.plist
文件中使用UNNotificationExtensionCategory
key聲明所支持category丘薛。其value默認(rèn)為String類型蔬捷。如果要支持多個(gè)category,可以將其修改為Array類型榔袋。
這里將UNNotificationExtensionCategory
key的value設(shè)置為customUICategory
。
Notification content app extension中Info.plist
鍵如下:
Key | Value |
---|---|
UNNotificationExtensionCategory (Required) |
字符串铡俐,或包含字符串的數(shù)組凰兑。字符串為使用UNNotificationCategory 類聲明category的標(biāo)志符。 |
UNNotificationExtensionInitialContentSizeRatio (Required) |
浮點(diǎn)值類型审丘,表示視圖控制器視圖初始大小吏够,為高度與寬度的比率滩报。在加載擴(kuò)展時(shí)脓钾,系統(tǒng)使用此值設(shè)置視圖控制器初始大小烂叔。例如,值為0.5 時(shí)脊另,視圖控制器高度為其寬度的一半。加載擴(kuò)展后也可以更改視圖控制器大小才顿。 |
UNNotificationExtensionDefaultContentHidden | 布爾類型匕争,默認(rèn)為NO 愧驱。設(shè)置為YES 時(shí)组砚,系統(tǒng)只顯示自定義通知界面;設(shè)置為NO 時(shí)掏颊,系統(tǒng)會(huì)顯示默認(rèn)通知界面糟红。自定義action按鈕和取消按鈕會(huì)永遠(yuǎn)顯示。 |
UNNotificationExtensionOverridesDefaultTitle | 布爾類型乌叶,默認(rèn)為NO 盆偿。設(shè)置為YES 時(shí),系統(tǒng)將控制器的title設(shè)置為通知title准浴;設(shè)置為NO 時(shí)事扭,系統(tǒng)將app名稱設(shè)置為通知title。 |
下圖左側(cè)為未隱藏default notification interface兄裂,右側(cè)為隱藏后界面:
8.2 添加視圖
Notification content extension 模板包含一個(gè)storyboard,通過(guò)為storyboard添加視圖來(lái)構(gòu)建自定義通知界面阳藻。例如晰奖,使用UILabel
顯示通知title、subtitle和body腥泥。還可以添加圖片匾南、視頻等非交互式內(nèi)容,無(wú)需為視圖提供任何初始內(nèi)容蛔外。
在iOS 12及以后蛆楞,還可以通過(guò)為Info.plist
添加UNNotificationExtensionUserInteractionEnabled
key溯乒,啟用交互式控件,如UIButton
豹爹、UISwitch
裆悄。該key為Boolean類型,值為YES
時(shí)支持交互式控件臂聋。
不要添加多個(gè)視圖控制器光稼,notification content app extension 只支持使用一個(gè)視圖控制器。
設(shè)置storyboard高度為160
孩等,并添加UILabel
艾君,如下:
8.3 UNNotificationExtension協(xié)議
UNNotificationContentExtension
協(xié)議為notification content app extension提供了入口,用于提供自定義通知頁(yè)面肄方。Notification Content Extension模板提供的NotificationViewController
類遵守該協(xié)議冰垄。該協(xié)議方法如下:
-
didReceive(_:)
:該方法必須實(shí)現(xiàn)。在該方法內(nèi)使用notification content配置視圖控制器权她。在視圖控制器可見(jiàn)時(shí)虹茶,該方法可能會(huì)被調(diào)用多次。具體的說(shuō)伴奥,新到達(dá)通知與已經(jīng)顯示通知threadIdentifier
相同時(shí)写烤,會(huì)再次調(diào)用該方法。該方法在擴(kuò)展程序的主線程中調(diào)用拾徙。 -
didReceive(_:completionHandler:)
:該方法可選實(shí)現(xiàn)洲炊。用戶點(diǎn)擊自定義按鈕時(shí)會(huì)調(diào)用該方法。該方法的UNNotificationResponse
參數(shù)可以用來(lái)區(qū)分用戶點(diǎn)擊的按鈕尼啡。處理完畢任務(wù)后暂衡,必須調(diào)用completion
塊。如果你實(shí)現(xiàn)了該方法崖瞭,則必須處理所有category的所有action狂巢。如果沒(méi)有實(shí)現(xiàn)該方法,用戶點(diǎn)擊按鈕后系統(tǒng)會(huì)將通知轉(zhuǎn)發(fā)給你的app书聚。
只可以修改NotificationViewController
視圖的高度唧领,不可修改其寬度。
實(shí)現(xiàn)didReceive(_:)
方法雌续,根據(jù)通知內(nèi)容設(shè)置通知標(biāo)題:
func didReceive(_ notification: UNNotification) {
// title = "pro648"
self.label?.text = String("Content Extension:\(notification.request.content.body)")
}
如下所示:
可以看到斩个,自定義視圖高度與寬度相等。修改UNNotificationExtensionInitialContentSizeRatio
值為0.5
驯杜,設(shè)置UNNotificationExtensionDefaultContentHidden
值為YES
受啥,設(shè)置UNNotificationExtensionOverridesDefaultTitle
值為YES
,并取消上述代碼中設(shè)置視圖控制器title代碼,運(yùn)行并發(fā)送遠(yuǎn)程通知:
更新didReceive(_:)
方法滚局,在顯示通知自定義界面時(shí)居暖,搖動(dòng)speakerLabel
;實(shí)現(xiàn)didReceive(_:completionHandler:)
方法藤肢,點(diǎn)擊Stop按鈕時(shí)太闺,停止搖動(dòng)speakerLabel
,Comment按鈕為UNTextInputNotificationAction
類型:
func didReceive(_ notification: UNNotification) {
title = "pro648"
self.label?.text = String("Content Extension:\(notification.request.content.body)")
speakerLabel.shake()
}
func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "stop" {
speakerLabel.text = "??"
speakerLabel.cancelShake()
completion(.doNotDismiss)
} else if response.actionIdentifier == "comment" {
completion(.dismissAndForwardAction)
} else {
completion(.dismiss)
}
}
}
extension UIView {
func shake() {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 1
animation.repeatCount = .infinity
animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0]
layer.add(animation, forKey: "shake")
}
func cancelShake() {
layer.removeAnimation(forKey: "shake")
}
}
運(yùn)行如下:
注冊(cè)
customUICategory
category和注冊(cè)calendarCategory
類似谤草,可以查看源碼AppDelegate.swift
部分跟束。
9. 關(guān)于用戶體驗(yàn)
- 使用完整的句子,正確的標(biāo)點(diǎn)符號(hào)丑孩,提供有價(jià)值信息冀宴。不要截?cái)嗤ㄖ械男畔ⅲm然在信息過(guò)長(zhǎng)時(shí)温学,系統(tǒng)會(huì)進(jìn)行這樣的操作略贮。避免告訴用戶導(dǎo)航到特別頁(yè)面,點(diǎn)擊特定按鈕仗岖,或執(zhí)行其它難以記住的任務(wù)逃延。
- 即時(shí)用戶沒(méi)有響應(yīng)通知,也不要為同一任務(wù)發(fā)送多個(gè)通知轧拄。用戶會(huì)在方便時(shí)處理通知揽祥,如果你的app為同一件事情發(fā)送多個(gè)通知,會(huì)占用通知中心整個(gè)界面檩电,用戶可能會(huì)關(guān)閉你的app通知功能拄丰。
- 通知中不要包含app名稱和icon。系統(tǒng)會(huì)自動(dòng)在每個(gè)通知頂部顯示這些信息俐末。
- 考慮提供詳細(xì)視圖料按。通過(guò)詳細(xì)視圖,可以在當(dāng)前環(huán)境對(duì)通知進(jìn)行操作卓箫,無(wú)需打開(kāi)app载矿。此視圖應(yīng)易識(shí)別、包含有價(jià)值信息烹卒,就像app正常擴(kuò)展闷盔。其可以包含圖片、視頻和其它內(nèi)容旅急,并可以在顯示時(shí)動(dòng)態(tài)更新逢勾。
- 避免提供破壞性功能。提供破壞性操作前坠非,需要確保用戶對(duì)該操作有清晰認(rèn)識(shí)敏沉,不存在誤解果正。destructive類型操作會(huì)用紅色顯示炎码。
Demo名稱:UserNotifications
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/UserNotifications
參考資料:
- Push Notifications Tutorial: Getting Started
- iOS 10 rich push notifications with media attachments
- Local and Remote Notification Programming Guide
- 活久見(jiàn)的重構(gòu) - iOS 10 UserNotifications 框架解析
- iOS 10 Day by Day :: Day 6 :: Notification Content Extensions
- UNLocationNotificationTrigger- Not working in simulator
- How can I convert my device token (NSData) into an NSString?
- Introduction to Notifications