iOS 中的通知主要分為 2 種,本地通知和遠(yuǎn)程通知。
本地通知
使用步驟
- 導(dǎo)入
UserNotifications模塊。 - 申請(qǐng)權(quán)限。
- 創(chuàng)建通知內(nèi)容
UNMutableNotificationContent,可以設(shè)置:
(1)title:通知標(biāo)題。
(2)subtitle:通知副標(biāo)題。
(3)body:通知體。
(4)sound:聲音。
(5)badge:角標(biāo)。
(6)userInfo:額外信息。
(7)categoryIdentifier:分類唯一標(biāo)識(shí)符。
(8)attachments:附件,可以是圖片、音頻和視頻,通過(guò)下拉通知顯示。 - 指定本地通知觸發(fā)條件,有 3 種觸發(fā)方式:
(1)UNTimeIntervalNotificationTrigger:一段時(shí)間后觸發(fā)。
(2)UNCalendarNotificationTrigger:指定日期時(shí)間觸發(fā)。
(3)UNLocationNotificationTrigger:根據(jù)位置觸發(fā)。 - 根據(jù)通知內(nèi)容和觸發(fā)條件創(chuàng)建
UNNotificationRequest。 - 將
UNNotificationRequest添加到UNUserNotificationCenter。
案例
- 申請(qǐng)授權(quán)(異步操作)。
import UserNotifications
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 請(qǐng)求通知權(quán)限
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { // 橫幅,聲音,標(biāo)記
(accepted, error) in
if !accepted {
print("用戶不允許通知")
}
}
return true
}
- 發(fā)送通知。
import CoreLocation
import UIKit
import UserNotifications
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// 一段時(shí)間后觸發(fā)
@IBAction func timeInterval(_ sender: Any) {
// 設(shè)置推送內(nèi)容
let content = UNMutableNotificationContent()
content.title = "你好"
content.subtitle = "Hi"
content.body = "這是一條基于時(shí)間間隔的測(cè)試通知"
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "feiji.wav"))
content.badge = 1
content.userInfo = ["username": "YungFan", "career": "Teacher"]
content.categoryIdentifier = "testUserNotifications1"
setupAttachment(content: content)
// 設(shè)置通知觸發(fā)器
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
// 設(shè)置請(qǐng)求標(biāo)識(shí)符
let requestIdentifier = "com.abc.testUserNotifications2"
// 設(shè)置一個(gè)通知請(qǐng)求
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
// 將通知請(qǐng)求添加到發(fā)送中心
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
// 指定日期時(shí)間觸發(fā)
@IBAction func dateInterval(_ sender: Any) {
// 設(shè)置推送內(nèi)容
let content = UNMutableNotificationContent()
content.title = "你好"
content.body = "這是一條基于日期的測(cè)試通知"
// 時(shí)間
var components = DateComponents()
components.year = 2021
components.month = 5
components.day = 20
// 每周一上午8點(diǎn)
// var components = DateComponents()
// components.weekday = 2 // 周一
// components.hour = 8 // 上午8點(diǎn)
// components.minute = 30 // 30分
// 設(shè)置通知觸發(fā)器
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
// 設(shè)置請(qǐng)求標(biāo)識(shí)符
let requestIdentifier = "com.abc.testUserNotifications3"
// 設(shè)置一個(gè)通知請(qǐng)求
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
// 將通知請(qǐng)求添加到發(fā)送中心
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
// 根據(jù)位置觸發(fā)
@IBAction func locationInterval(_ sender: Any) {
// 設(shè)置推送內(nèi)容
let content = UNMutableNotificationContent()
content.title = "你好"
content.body = "這是一條基于位置的測(cè)試通知"
// 位置
let coordinate = CLLocationCoordinate2D(latitude: 31.29065118, longitude: 118.3623587)
let region = CLCircularRegion(center: coordinate, radius: 500, identifier: "center")
region.notifyOnEntry = true // 進(jìn)入此范圍觸發(fā)
region.notifyOnExit = false // 離開此范圍不觸發(fā)
// 設(shè)置觸發(fā)器
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
// 設(shè)置請(qǐng)求標(biāo)識(shí)符
let requestIdentifier = "com.abc.testUserNotifications"
// 設(shè)置一個(gè)通知請(qǐng)求
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
// 將通知請(qǐng)求添加到發(fā)送中心
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
}
extension ViewController {
func setupAttachment(content: UNMutableNotificationContent) {
let imageURL = Bundle.main.url(forResource: "img", withExtension: ".png")!
do {
let imageAttachment = try UNNotificationAttachment(identifier: "iamgeAttachment", url: imageURL, options: nil)
content.attachments = [imageAttachment]
} catch {
print(error.localizedDescription)
}
}
}
遠(yuǎn)程通知(消息推送)
遠(yuǎn)程通知是指在聯(lián)網(wǎng)的情況下,由遠(yuǎn)程服務(wù)器推送給客戶端的通知,又稱 APNs(Apple Push Notification Services)。在聯(lián)網(wǎng)狀態(tài)下,所有設(shè)備都會(huì)與 Apple 服務(wù)器建立長(zhǎng)連接,因此不管應(yīng)用是打開還是關(guān)閉的情況,都能接收到服務(wù)器推送的遠(yuǎn)程通知。

遠(yuǎn)程通知流程.png
實(shí)現(xiàn)原理
- App 打開后首先發(fā)送 UDID 和 BundleID 給 APNs 注冊(cè),并返回 deviceToken(圖中步驟 1,2,3)。
- App 獲取 deviceToken 后,通過(guò) API 將 App 的相關(guān)信息和 deviceToken 發(fā)送給應(yīng)用服務(wù)器,服務(wù)器將其記錄下來(lái)。(圖中步驟 4)
- 當(dāng)要推送通知時(shí),應(yīng)用服務(wù)器按照 App 的相關(guān)信息找到存儲(chǔ)的 deviceToken,將通知和 deviceToken 發(fā)送給 APNs。(圖中步驟 5)
- APNs 通過(guò) deviceToken,找到指定設(shè)備的指定 App, 并將通知推送出去。(圖中步驟 6)
實(shí)現(xiàn)步驟
- 在開發(fā)者網(wǎng)站的 Identifiers 中添加 App IDs,并在 Capabilities 中開啟 Push Notifications。
- 在 Certificates 中創(chuàng)建一個(gè) Apple Push Notification service SSL (Sandbox & Production) 的 APNs 證書并關(guān)聯(lián)第一步中的 App IDs,然后將證書下載到本地安裝(安裝完可以導(dǎo)出 P12 證書)。
- 在項(xiàng)目中選擇 Capability,接著開啟 Push Notifications,然后在 Background Modes 中勾選 Remote notifications。
- 申請(qǐng)權(quán)限。
- 通過(guò)
UIApplication.shared.registerForRemoteNotifications()向 APNs 請(qǐng)求 deviceToken。 - 通過(guò)
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)獲取 deviceToken。如果正常獲取到 deviceToken,即表示注冊(cè)成功,可以進(jìn)行遠(yuǎn)程通知的推送,最后需要將其發(fā)送給應(yīng)用服務(wù)器。注意:- App 重新啟動(dòng)后,deviceToken 不會(huì)變化。
- App 卸載后重新安裝,deviceToken 發(fā)生變化。
- 通知測(cè)試。
AppDelegate
import UserNotifications
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 請(qǐng)求通知權(quán)限
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
accepted, _ in
if !accepted {
print("用戶不允許通知。")
}
}
// 向APNs請(qǐng)求deviceToken
UIApplication.shared.registerForRemoteNotifications()
return true
}
// deviceToken請(qǐng)求成功回調(diào)
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
var deviceTokenString = String()
let bytes = [UInt8](deviceToken)
for item in bytes {
deviceTokenString += String(format: "%02x", item & 0x000000FF)
}
// 打印獲取到的token字符串
print(deviceTokenString)
// 通過(guò)網(wǎng)絡(luò)將token發(fā)送給服務(wù)端
}
// deviceToken請(qǐng)求失敗回調(diào)
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error.localizedDescription)
}
}
注意:遠(yuǎn)程通知不支持模擬器(直接進(jìn)入deviceToken請(qǐng)求失敗回調(diào)),必須在真機(jī)測(cè)試。
測(cè)試
真機(jī)測(cè)試
- 將 App 安裝到真機(jī)上。
- 通過(guò)軟件(如 APNs)或者第三方進(jìn)行測(cè)試,但都需要進(jìn)行相關(guān)內(nèi)容的設(shè)置。
(1)證書方式需要:P12 證書 + Bundle Identifier + deviceToken。
(2)Token 方式需要:P8 AuthKey + Team ID + Key ID + Bundle Identifier + deviceToken。
模擬器測(cè)試—使用JSON文件
- JSON文件。
{
"aps":{
"alert":{
"title":"測(cè)試",
"subtitle":"遠(yuǎn)程推送",
"body":"這是一條從遠(yuǎn)處而來(lái)的通知"
},
"sound":"default",
"badge":1
}
}
- 命令。
xcrun simctl push booted developer.yf.TestUIKit /Users/yangfan/Desktop/playload.json
模擬器測(cè)試—使用APNS文件
另一種方法是將 APNs 文件直接拖到 iOS 模擬器中。準(zhǔn)備一個(gè)后綴名為.apns的文件,其內(nèi)容和上面的 JSON 文件差不多,但是添加了一個(gè)Simulator Target Bundle,用于描述 App 的Bundle Identifier。
- APNs文件。
{
"Simulator Target Bundle": "developer.yf.TestUIKit",
"aps":{
"alert":{
"title":"測(cè)試",
"subtitle":"遠(yuǎn)程推送",
"body":"這是一條從遠(yuǎn)處而來(lái)的通知"
},
"sound":"default",
"badge":1
}
}
前臺(tái)處理
默認(rèn)情況下,App 只有在后臺(tái)才能收到通知提醒,在前臺(tái)無(wú)法收到通知提醒,如果前臺(tái)也需要提醒可以進(jìn)行如下處理。
- 創(chuàng)建 UNUserNotificationCenterDelegate。
class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
// 前臺(tái)展示通知
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// 前臺(tái)通知一般不設(shè)置badge
completionHandler([.list, .banner, .sound])
// 如果不想顯示某個(gè)通知,可以直接用 []
// completionHandler([])
}
}
- 設(shè)置代理。
class AppDelegate: UIResponder, UIApplicationDelegate {
// 自定義通知回調(diào)類,實(shí)現(xiàn)通知代理
let notificationHandler = NotificationHandler()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 設(shè)置代理
UNUserNotificationCenter.current().delegate = notificationHandler
return true
}
}
角標(biāo)設(shè)置
- 不論是本地還是遠(yuǎn)程通知,前臺(tái)通知一般不會(huì)設(shè)置角標(biāo)提醒,所以只需要針對(duì)后臺(tái)通知處理角標(biāo)即可。
- 通知的角標(biāo)不需要手動(dòng)設(shè)置,會(huì)自動(dòng)根據(jù)通知進(jìn)行設(shè)置。
設(shè)置
// 手動(dòng)添加角標(biāo)
UIApplication.shared.applicationIconBadgeNumber = 10
// 清理角標(biāo)
UIApplication.shared.applicationIconBadgeNumber = 0