通知相關(guān)系列文章
iOS10 之前通知使用介紹
[iOS] 通知詳解: UIUserNotification
iOS10 相關(guān)API
[iOS] 通知詳解:iOS 10 UserNotifications API
iOS10 本地/遠程通知
[iOS] 通知詳解: iOS 10 UserNotifications
iOS10 通知附加包
[iOS] 通知詳解: iOS 10 UserNotifications -- 附加包Media Attachments
iOS10 自定義UI
[iOS] 通知詳解: iOS 10 UserNotifications -- 自定義通知UI
本文主要是介紹iOS10之前的推送處理相關(guān)API及示例降淮,很多文章都是將iOS10之前的API和iOS10新出的一起介紹疗隶,個人感覺比較混亂茉继,而且不夠清晰不见,所以才重新總結(jié)了一些文章并結(jié)合自己的使用經(jīng)驗,將 iOS10 之前的使用和iOS10之后的進行歸納持寄。
通知權(quán)限請求
不管是本地通知源梭,還是遠程通知,都需要向用戶申請通知的權(quán)限
UIUserNotificationSettings (iOS 8 ---- iOS 10)
UIUserNotificationSettings 主要由一個初始化方法來完成配置:
// types:通知的類型 見 UIUserNotificationType
// categories:自定義行為按鈕稍味,見 UIUserNotificationCategory废麻,可傳nil
public convenience init(types: UIUserNotificationType, categories: Set<UIUserNotificationCategory>?)
UIUserNotificationType 通知類型
public struct UIUserNotificationType : OptionSet {
public init(rawValue: UInt)
// 角標
public static var badge: UIUserNotificationType { get }
// 聲音
public static var sound: UIUserNotificationType { get } // the application may play a sound upon a notification being received
// 彈框
public static var alert: UIUserNotificationType { get } // the application may display an alert upon a notification being received
}
一般使用(不帶有動作按鈕)
- 注冊通知設置
// categories 傳nil,則通知沒有額外的動作按鈕
let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil)
UIApplication.shared.registerUserNotificationSettings(setting)
registerUserNotificationSettings 方法調(diào)用后模庐,成功后會以代理的方式進行反饋
// registerUserNotificationSettings 回調(diào)代理
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
// 注冊 deviceToken
application.registerForRemoteNotifications()
}
這里我們需要調(diào)用registerForRemoteNotifications烛愧,來注冊通知,獲取deviceToken掂碱,也會以代理的方式進行反饋
// registerForRemoteNotifications 成功的回調(diào)怜姿,獲取到token
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
// Next do ...
// 一般是發(fā)送給自己的服務后臺,或者第三方的推送服務器后臺
}
// registerForRemoteNotifications 失敗的回調(diào)
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error)
}
到此疼燥,通知的權(quán)限申請及遠程注冊就完成了沧卢,接下來就是發(fā)送通知,本地或者遠程醉者。
此時但狭,通知的彈框是沒有其他動作按鈕的违寿,彈框下拉后,只有消息標題熟空,內(nèi)容等信息,沒有額外的動作按鈕
帶有動作選擇的通知
帶有動作的通知搞莺,需要設置其 categories 息罗,涉及到以下幾個類的配置
UIUserNotificationCategory
攜帶動作的Category,一般是使用其可變實例 UIMutableUserNotificationCategory來添加多個行為:
// 唯一標識符
open var identifier: String?
// Sets the UIUserNotificationActions in the order to be displayed for the specified context
open func setActions(_ actions: [UIUserNotificationAction]?, for context: UIUserNotificationActionContext)
UIUserNotificationActions
動作實例才沧,一般是使用其可變實例 UIMutableUserNotificationAction 來配置更多的行為
// 唯一標識符
open var identifier: String?
// 按鈕 title
open var title: String?
// 用戶點擊該按鈕時的行為迈喉,有兩種
/*public enum UIUserNotificationActionBehavior : UInt {
// 一般的點擊事件
case `default` // the default action behavior
// 點擊按鈕后會彈出輸入框,可以輸入一段文字快捷回復
case textInput // system provided action behavior, allows text input from the user
}
*/
// The behavior of this action when the user activates it.
@available(iOS 9.0, *)
open var behavior: UIUserNotificationActionBehavior
// Parameters that can be used by some types of actions.
@available(iOS 9.0, *)
open var parameters: [AnyHashable : Any]
// 按鈕響應事件的模式温圆,有兩種
/*public enum UIUserNotificationActivationMode : UInt {
// 在該模式下挨摸,點擊按鈕后會打開app,可以在代理方法中做一些跳轉(zhuǎn)操作
case foreground // activates the application in the foreground
// 在該模式下岁歉,點擊后不會打開app得运,可以后臺做一些操作
case background // activates the application in the background, unless it's already in the foreground
}
*/
// How the application should be activated in response to the action.
open var activationMode: UIUserNotificationActivationMode
// 是否需要授權(quán)
// Whether this action is secure and should require unlocking before being performed. If the activation mode is UIUserNotificationActivationModeForeground, then the action is considered secure and this property is ignored.
open var isAuthenticationRequired: Bool
// 當需要執(zhí)行某些破壞性的操作時,需要醒目提醒锅移,紅色的按鈕
// Whether this action should be indicated as destructive when displayed.
open var isDestructive: Bool
示例:
let ok = UIMutableUserNotificationAction()
ok.identifier = "okidentifier"
ok.activationMode = . foreground // 這里需要打開app熔掺,所以設置為foreground模式
ok.title = "查看"
ok.isDestructive = false
ok.isAuthenticationRequired = false
let cancel = UIMutableUserNotificationAction()
cancel.identifier = "cancelidentifier"
cancel.activationMode = .background // 這里不需要打開app,所以設置為background模式
cancel.title = "關(guān)閉"
cancel.isDestructive = true
cancel.isAuthenticationRequired = false
let category = UIMutableUserNotificationCategory()
category.identifier = "categoryidentifier"
category.setActions([ok, cancel], for: .default)
let set = Set([category])
let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: set)
UIApplication.shared.registerUserNotificationSettings(setting)
效果非剃,彈框下拉后置逻,除了消息標題,內(nèi)容等信息备绽,還有添加的動作按鈕
添加的按鈕點擊后券坞,會調(diào)用下面的代理方法,本地通知和遠程通知調(diào)用的方法不一樣
// ios 8 --- ios 10
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
print("handleActionWithIdentifier1 \(identifier)")
completionHandler()
}
// ios 9 --- ios 10
// 本地推送的UIMutableUserNotificationAction回調(diào)
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
print("handleActionWithIdentifier2 \(identifier)")
completionHandler()
}
如果將創(chuàng)建 UIMutableUserNotificationAction 實例對象時的屬性 behavior 設置為 .textInput肺素,則點擊按鈕時恨锚,會彈出輸入框:
輸入的值,可以在代理方法的 responseInfo 獲缺睹摇:
// ios 8 --- ios 10
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
let input = responseInfo[UIUserNotificationActionResponseTypedTextKey]
print(input)
completionHandler()
}
本地通知
UILocalNotification(iOS 4.0 ---- iOS 10.0)
UILocalNotification 基本屬性
UILocalNotification 常用的基本屬性有以下幾個:
// 本地通知指定處罰時間
open var fireDate: Date?
// 通知彈框的內(nèi)容
open var alertBody: String?
// 通知彈框的標題
@available(iOS 8.2, *)
open var alertTitle: String?
// 通知來時的提示音文件名稱(需要加擴展名)眠冈,UILocalNotificationDefaultSoundName為系統(tǒng)設置的默認提示音
open var soundName: String?
// 角標消息數(shù)量
// badge
open var applicationIconBadgeNumber: Int // 0 means no change. defaults to 0
// 通知代理方法調(diào)用后攜帶的參數(shù)內(nèi)容
// user info
open var userInfo: [AnyHashable : Any]?
// 動作按鈕,此處的值要和設置的 UIUserNotificationCategory 實例的identifier屬性值一致
// category identifer of the local notification, as set on a UIUserNotificationCategory and passed to +[UIUserNotificationSettings settingsForTypes:categories:]
@available(iOS 8.0, *)
open var category: String?
// 觸發(fā)時間所屬的時區(qū)菌瘫,默認是本機設定時區(qū)
open var timeZone: TimeZone?
// 重復觸發(fā)的頻率蜗顽,詳見NSCalendar.Unit,例如 day:按天雨让;month:按月雇盖;0 為不重復
open var repeatInterval: NSCalendar.Unit
// 指定日歷,默認即可
open var repeatCalendar: Calendar?
// 當進入/離開某個地理范圍時觸發(fā)本地通知栖忠;例如進入某地/某商場范圍觸發(fā)本地通知
// 需要有 "when-in-use" 的定位權(quán)限
@available(iOS 8.0, *)
@NSCopying open var region: CLRegion?
// 若為YES崔挖,每次超出/進入該范圍都會觸發(fā)贸街;若為NO,則只觸發(fā)一次狸相;默認為YES
@available(iOS 8.0, *)
open var regionTriggersOnce: Bool
// 鎖屏狀態(tài)下薛匪,是否顯示滑動的提示語,默認YES脓鹃,iOS11好像沒有這個提示語了
open var hasAction: Bool
// 鎖屏狀態(tài)下逸尖,顯示滑動的提示語,默認為“滑動解鎖xxx”瘸右,如果設置此值為“打開xxx”娇跟,則是“滑動打開xxx”
open var alertAction: String?
// 打開通知進入應用時的啟動圖片
open var alertLaunchImage: String?
添加/執(zhí)行通知的方法
在通知設置完成后,調(diào)用下面的方法來執(zhí)行/添加到執(zhí)行隊列
// 將本地通知添加到調(diào)度池太颤,定時發(fā)送 fireDate
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
// 立即發(fā)送
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
本地通知的取消
// 取消某個本地通知苞俘,參數(shù)為通知的實例對象
open func cancelLocalNotification(_ notification: UILocalNotification)
// 取消所有待你執(zhí)行的本地通知
open func cancelAllLocalNotifications()
本地通知的處理
收到本地通知時,分兩種情況來處理獲取到的通知:
- APP在前臺運行龄章,或者單機Home鍵切換到后臺運行(未雙擊Home鍵完全退出)
會調(diào)用下面的代理方法
當前APP在前臺運行時吃谣,會立即調(diào)用該方法;
當APP從后臺被喚起時做裙,點擊推送消息打開時調(diào)用
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
// 獲取當前APP的狀態(tài)
let state = application.applicationState
var msg = ""
// 處在活躍狀態(tài)基协,前臺運行
if state == .active {
// qiantai
msg += "qian tai"
} else if state == .inactive {
// 處于不活躍狀態(tài),后臺或后臺被系統(tǒng)掛起
// houtai
msg += "hou tai"
}
// 注冊通知時添加的 userInfo 信息
msg += "\n\(notification.userInfo)"
self.alert(msg)
application.cancelLocalNotification(notification)
}
- APP 被殺死(雙擊Home鍵菇用,從后臺關(guān)閉)
此時澜驮,應該從 didFinishLaunchingWithOptions 方法獲取通知內(nèi)容
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// 應用被殺死后,如果來了通知惋鸥,會在此處獲取推送內(nèi)容
if let localNoti = launchOptions?[UIApplication.LaunchOptionsKey.localNotification] as? UILocalNotification {
// NEXT TO DO ...
print(localNoti.userInfo)
}
return true
}
需要注意的是杂穷,此時雖然獲取到了通知內(nèi)容,但是卦绣,跟視圖控制器可能還沒有加載耐量,業(yè)務的處理邏輯應該放到跟視圖加載完成后再去處理。
如果某個推送已讀滤港,需要重置角標數(shù)量廊蜒,可參考下面的方法
// 獲取角標數(shù)量
var badegNumber = UIApplication.shared.applicationIconBadgeNumber
// 修改角標數(shù)量
badegNumber -= 1
// 重新賦值角標數(shù)量
UIApplication.shared.applicationIconBadgeNumber = badegNumber >= 0 ? badegNumber: 0
遠程通知 APNs(Apple Push Notification Services)
遠程推送獲取權(quán)限、自定義快捷操作以及獲取 deviceToken 和本地通知相同溅漾,不同的只是收到通知后山叮,回調(diào)的代理方法不一樣。
- 遠程通知必須使用真機
- 遠程通知必須使用真機
- 遠程通知必須使用真機
測試推送服務
在開始之前添履,先介紹一個發(fā)送遠程通知的軟件:NWPusher屁倔,可以在GitHub下載后自己編譯運行,也可以直接下載其二進制文件使用NWPusher app暮胧,打開后如下圖:
如果不是真機锐借,是獲取不到deviceToken的
payload的內(nèi)容模版為:
{"aps":{"alert":"Testing.. (0)","badge":1,"sound":"default"}}
key值aps是蘋果定義的问麸,推送的消息Json格式一定要是這樣,除了此key钞翔,還有以下key值是蘋果定義的:
- alert :彈框內(nèi)容严卖,可以是 Dictionary
- badge:角標的數(shù)量
- sound:收到通知時播放的聲音文件名,需要提前添加到Bundle中
- content-availabel:如果其值為1布轿,則是靜默通知哮笆,且不能設置alert,sound,badge,否則無效
- category:一組額外的快捷按鈕標識符驮捍,即UIMutableUserNotificationCategory的identifier屬性
注意: payload的長度是有限制的,
iOS 8以下是 256字節(jié)脚曾,
iOS 8 --- iOS 9是2k东且,
iOS 9+ 是4k,
超過限制的消息本讥,蘋果后臺是拒絕推送的珊泳。
如果需要添加自己的內(nèi)容,直接在里面的字典添加自定義的key即可拷沸,例如:
{"aps":{"alert":"Testing.. (0)","badge":1,"sound":"default","extinfo": {}}}
然后色查,點擊右下角的 push按鈕即可發(fā)送一個遠程通知。
PS:如果軟件長時間不操作撞芍,點擊push的報錯秧了,可點擊右上角的Reconnect 重新連接服務即可!P蛭蕖验毡!
一般遠程通知
一般的遠程通知注冊和本地通知的注冊一樣,遠程通知不需要本地創(chuàng)建通知對象帝嗡,而是發(fā)送 payload 由系統(tǒng)來創(chuàng)建對應的對象晶通,payload 就相當于給通知賦值:
let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil)
UIApplication.shared.registerUserNotificationSettings(setting)
測試發(fā)送一個 Payload:
{"aps":{"alert":"Remote Notification Test Info","badge":1,"sound":"default","extinfo": {"info": "Test remote info"}}}
使用上面介紹的軟件發(fā)送:
帶快捷交互的通知
如果想要帶有快捷交互的通知,需要在Payload中添加 category 字段哟玷,且其值要與注冊通知時創(chuàng)建的 UIMutableUserNotificationCategory 實例的屬性identifier值一致:
例如狮辽,注冊的通知:
func registerAPN() {
let ok = UIMutableUserNotificationAction()
ok.identifier = "okidentifier"
ok.activationMode = . foreground // 這里需要打開app,所以設置為foreground模式
ok.title = "查看"
ok.isDestructive = false
ok.isAuthenticationRequired = false
ok.behavior = .default
let cancel = UIMutableUserNotificationAction()
cancel.identifier = "cancelidentifier"
cancel.activationMode = .background // 這里不需要打開app巢寡,所以設置為background模式
cancel.title = "關(guān)閉"
cancel.isDestructive = true
cancel.isAuthenticationRequired = false
let category = UIMutableUserNotificationCategory()
category.identifier = "categoryidentifier"
category.setActions([ok, cancel], for: .default)
let set = Set([category])
let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: set)
UIApplication.shared.registerUserNotificationSettings(setting)
}
將上面的Payload修改為:
{"aps":{"alert":"Remote Notification Test Info","badge":1,"sound":"default","category": "categoryidentifier","extinfo": {"info": "Test remote info"}}}
再次發(fā)送通知:
這時點擊對應的快捷按鈕喉脖,會調(diào)用系統(tǒng)代理方法:
// 遠程推送的UIMutableUserNotificationAction回調(diào)
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
if identifier == "okidentifier" {
print("ok")
} else {
print("cancel")
}
}
其中的 userInfo 即是我們發(fā)送的Payload中的內(nèi)容,在這里處理相應的業(yè)務邏輯即可抑月;
如果动看,快捷操作的類型是輸入框(iOS 9 --- iOS 10),只需要將UIMutableUserNotificationAction示例對象的屬性behavior修改為.textInput 即可爪幻,這時輸入完成會調(diào)用方法:
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
let input = responseInfo[UIUserNotificationActionResponseTypedTextKey]
print(input)
}
遠程通知的處理
同本地通知一樣菱皆,遠程通知的處理也分兩種情況:
- app 處于前臺或后臺(未雙擊Home鍵须误,將app殺死)
- app 被殺死,雙擊Home鍵仇轻,將app從后臺殺死
以上兩種情況京痢,如果來了遠程通知,都會調(diào)用下面的代理方法:
// iOS 7 +
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let state = UIApplication.shared.applicationState
var msg = ""
if state == .active {
// qiantai
msg += "active"
} else {
// houtai
msg += "inactive"
}
print("Receive remote notification at \(msg)")
print(userInfo)
completionHandler(.noData)
}
// iOS 3 -- iOS 10
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
print("didReceiveRemoteNotification old")
}
iOS 7以后主要是使用第一個方法篷店,只有在iOS 7以下的版本才會走第二個代理方法祭椰,現(xiàn)在幾乎是使用不到了。
如果app正在前臺運行疲陕,此時來了遠程消息方淤,會直接調(diào)用該代理方法,不會有彈框蹄殃,通過 userInfo 可以獲取推送的Playload信息携茂;
如果app在后臺運行或被系統(tǒng)掛起或被殺死,如果來了遠程消息诅岩,就有彈框提醒讳苦,此時不會調(diào)用該方法,當點擊消息打開app的時候吩谦,才會調(diào)用鸳谜。
當app被殺死后,也可能會走didFinishLaunchingWithOptions方法:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.registerAPN()
// 應用被殺死后式廷,如果來了通知咐扭,會在此處獲取推送內(nèi)容
if let pushNiti = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [String: Any] {
print(pushNiti)
}
return true
}
也可以添加如上的處理方法,現(xiàn)在一般都會走didReceiveRemoteNotification代理方法滑废。
靜默push iOS 7 +
靜默push是沒有彈框草描,沒有聲音,沒有任何提醒的push策严,但是他能喚起你的app維持3分鐘的運行穗慕,前提是你的app被切入后臺運行或被系統(tǒng)掛起,而不是雙擊Home鍵殺死app妻导。完全退出后逛绵,也是接收不到靜默push的。他是iOS 7之后才出現(xiàn)的一種推送方式倔韭。
靜默push的前提是:
在payload中术浪,沒有alert及sound字段,而且添加content-available字段, 并設置其值為1:
{"aps":{"content-available":"1","extinfo": {"info": "Test remote info"}}}
同樣寿酌,我們可以在接收一般通知的代理方法中來處理靜默push的信息:
// iOS 7 +
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let state = UIApplication.shared.applicationState
var msg = ""
if state == .active {
// qiantai
msg += "active"
} else {
// houtai
msg += "inactive"
}
print("Receive remote notification at \(msg)")
print(userInfo)
completionHandler(.noData)
}
一般我們需要在不打擾用戶的情況下更新一些數(shù)據(jù)時胰苏,可以選擇使用靜默push,來后臺下載更新相關(guān)的數(shù)據(jù)醇疼。
這里completionHandler的參數(shù)是UIBackgroundFetchResult類型硕并,他有三個值:
@available(iOS 7.0, *)
public enum UIBackgroundFetchResult : UInt {
// 新數(shù)據(jù)下載成功
case newData
// 沒有新數(shù)據(jù)需要下載
case noData
// 新數(shù)據(jù)下載失敗
case failed
}
通知中心快捷回復
同本地通知一樣法焰,在通知中心快捷回復,只需要在創(chuàng)建UIMutableUserNotificationAction 實例對象時的屬性 behavior 設置為 .textInput倔毙,則點擊按鈕時埃仪,會彈出輸入框,同樣是在下面的代理方法中可以獲取到輸入的內(nèi)容:
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
let input = responseInfo[UIUserNotificationActionResponseTypedTextKey]
print(input)
completionHandler()
}
到此陕赃,iOS10以下的通知處理基本就結(jié)束了卵蛉,但是在iOS10中,這些處理方式都廢棄了么库,使用新的API來處理通知:UserNotifications
后臺任務示例代碼
最后給一個后臺任務的示例代碼
func backgroundTask() {
let app = UIApplication.shared
var taskid = UIBackgroundTaskIdentifier.invalid
taskid = app.beginBackgroundTask {
app.endBackgroundTask(taskid)
taskid = .invalid
}
let queue = DispatchQueue(label: "backgroundTaskQueue")
queue.async {
while true {
let time = app.backgroundTimeRemaining
if time < 5 {
break
}
Thread.sleep(forTimeInterval: 1)
}
}
app.endBackgroundTask(taskid)
taskid = .invalid
}