iOS 中的通知主要分为 2 种,本地通知和远程通知。
本地通知
使用步骤
- 导入
UserNotifications
模块。 - 申请权限。
- 创建通知内容
UNMutableNotificationContent
,可以设置:
(1)title:通知标题。
(2)subtitle:通知副标题。
(3)body:通知体。
(4)sound:声音。
(5)badge:角标。
(6)userInfo:额外信息。
(7)categoryIdentifier:分类唯一标识符。
(8)attachments:附件,可以是图片、音频和视频,通过下拉通知显示。 - 指定本地通知触发条件,有 3 种触发方式:
(1)UNTimeIntervalNotificationTrigger
:一段时间后触发。
(2)UNCalendarNotificationTrigger
:指定日期时间触发。
(3)UNLocationNotificationTrigger
:根据位置触发。 - 根据通知内容和触发条件创建
UNNotificationRequest
。 - 将
UNNotificationRequest
添加到UNUserNotificationCenter
。
案例
- 申请授权(异步操作)。
import UserNotifications
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 请求通知权限
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { // 横幅,声音,标记
(accepted, error) in
if !accepted {
print("用户不允许通知")
}
}
return true
}
- 发送通知。
import CoreLocation
import UIKit
import UserNotifications
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// 一段时间后触发
@IBAction func timeInterval(_ sender: Any) {
// 设置推送内容
let content = UNMutableNotificationContent()
content.title = "你好"
content.subtitle = "Hi"
content.body = "这是一条基于时间间隔的测试通知"
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "feiji.wav"))
content.badge = 1
content.userInfo = ["username": "YungFan", "career": "Teacher"]
content.categoryIdentifier = "testUserNotifications1"
setupAttachment(content: content)
// 设置通知触发器
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
// 设置请求标识符
let requestIdentifier = "com.abc.testUserNotifications2"
// 设置一个通知请求
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
// 将通知请求添加到发送中心
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
// 指定日期时间触发
@IBAction func dateInterval(_ sender: Any) {
// 设置推送内容
let content = UNMutableNotificationContent()
content.title = "你好"
content.body = "这是一条基于日期的测试通知"
// 时间
var components = DateComponents()
components.year = 2021
components.month = 5
components.day = 20
// 每周一上午8点
// var components = DateComponents()
// components.weekday = 2 // 周一
// components.hour = 8 // 上午8点
// components.minute = 30 // 30分
// 设置通知触发器
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
// 设置请求标识符
let requestIdentifier = "com.abc.testUserNotifications3"
// 设置一个通知请求
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
// 将通知请求添加到发送中心
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
// 根据位置触发
@IBAction func locationInterval(_ sender: Any) {
// 设置推送内容
let content = UNMutableNotificationContent()
content.title = "你好"
content.body = "这是一条基于位置的测试通知"
// 位置
let coordinate = CLLocationCoordinate2D(latitude: 31.29065118, longitude: 118.3623587)
let region = CLCircularRegion(center: coordinate, radius: 500, identifier: "center")
region.notifyOnEntry = true // 进入此范围触发
region.notifyOnExit = false // 离开此范围不触发
// 设置触发器
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
// 设置请求标识符
let requestIdentifier = "com.abc.testUserNotifications"
// 设置一个通知请求
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
// 将通知请求添加到发送中心
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)
}
}
}
远程通知(消息推送)
远程通知是指在联网的情况下,由远程服务器推送给客户端的通知,又称 APNs(Apple Push Notification Services)。在联网状态下,所有设备都会与 Apple 服务器建立长连接,因此不管应用是打开还是关闭的情况,都能接收到服务器推送的远程通知。
实现原理
- App 打开后首先发送 UDID 和 BundleID 给 APNs 注册,并返回 deviceToken(图中步骤 1,2,3)。
- App 获取 deviceToken 后,通过 API 将 App 的相关信息和 deviceToken 发送给应用服务器,服务器将其记录下来。(图中步骤 4)
- 当要推送通知时,应用服务器按照 App 的相关信息找到存储的 deviceToken,将通知和 deviceToken 发送给 APNs。(图中步骤 5)
- APNs 通过 deviceToken,找到指定设备的指定 App, 并将通知推送出去。(图中步骤 6)
实现步骤
- 在开发者网站的 Identifiers 中添加 App IDs,并在 Capabilities 中开启 Push Notifications。
- 在 Certificates 中创建一个 Apple Push Notification service SSL (Sandbox & Production) 的 APNs 证书并关联第一步中的 App IDs,然后将证书下载到本地安装(安装完可以导出 P12 证书)。
- 在项目中选择 Capability,接着开启 Push Notifications,然后在 Background Modes 中勾选 Remote notifications。
- 申请权限。
- 通过
UIApplication.shared.registerForRemoteNotifications()
向 APNs 请求 deviceToken。 - 通过
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
获取 deviceToken。如果正常获取到 deviceToken,即表示注册成功,可以进行远程通知的推送,最后需要将其发送给应用服务器。注意:- App 重新启动后,deviceToken 不会变化。
- App 卸载后重新安装,deviceToken 发生变化。
- 通知测试。
AppDelegate
import UserNotifications
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 请求通知权限
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
accepted, _ in
if !accepted {
print("用户不允许通知。")
}
}
// 向APNs请求deviceToken
UIApplication.shared.registerForRemoteNotifications()
return true
}
// deviceToken请求成功回调
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)
// 通过网络将token发送给服务端
}
// deviceToken请求失败回调
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error.localizedDescription)
}
}
注意:远程通知不支持模拟器(直接进入deviceToken请求失败回调),必须在真机测试。
测试
真机测试
- 将 App 安装到真机上。
- 通过软件(如 APNs)或者第三方进行测试,但都需要进行相关内容的设置。
(1)证书方式需要:P12 证书 + Bundle Identifier + deviceToken。
(2)Token 方式需要:P8 AuthKey + Team ID + Key ID + Bundle Identifier + deviceToken。
模拟器测试—使用JSON文件
- JSON文件。
{
"aps":{
"alert":{
"title":"测试",
"subtitle":"远程推送",
"body":"这是一条从远处而来的通知"
},
"sound":"default",
"badge":1
}
}
- 命令。
xcrun simctl push booted developer.yf.TestUIKit /Users/yangfan/Desktop/playload.json
模拟器测试—使用APNS文件
另一种方法是将 APNs 文件直接拖到 iOS 模拟器中。准备一个后缀名为.apns
的文件,其内容和上面的 JSON 文件差不多,但是添加了一个Simulator Target Bundle
,用于描述 App 的Bundle Identifier
。
- APNs文件。
{
"Simulator Target Bundle": "developer.yf.TestUIKit",
"aps":{
"alert":{
"title":"测试",
"subtitle":"远程推送",
"body":"这是一条从远处而来的通知"
},
"sound":"default",
"badge":1
}
}
前台处理
默认情况下,App 只有在后台才能收到通知提醒,在前台无法收到通知提醒,如果前台也需要提醒可以进行如下处理。
- 创建 UNUserNotificationCenterDelegate。
class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
// 前台展示通知
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// 前台通知一般不设置badge
completionHandler([.list, .banner, .sound])
// 如果不想显示某个通知,可以直接用 []
// completionHandler([])
}
}
- 设置代理。
class AppDelegate: UIResponder, UIApplicationDelegate {
// 自定义通知回调类,实现通知代理
let notificationHandler = NotificationHandler()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 设置代理
UNUserNotificationCenter.current().delegate = notificationHandler
return true
}
}
角标设置
- 不论是本地还是远程通知,前台通知一般不会设置角标提醒,所以只需要针对后台通知处理角标即可。
- 通知的角标不需要手动设置,会自动根据通知进行设置。
设置
// 手动添加角标
UIApplication.shared.applicationIconBadgeNumber = 10
// 清理角标
UIApplication.shared.applicationIconBadgeNumber = 0