iOS14开发- 通知

iOS 中的通知主要分为 2 种,本地通知和远程通知。

本地通知

使用步骤

  1. 导入UserNotifications模块。
  2. 申请权限。
  3. 创建通知内容UNMutableNotificationContent,可以设置:
    (1)title:通知标题。
    (2)subtitle:通知副标题。
    (3)body:通知体。
    (4)sound:声音。
    (5)badge:角标。
    (6)userInfo:额外信息。
    (7)categoryIdentifier:分类唯一标识符。
    (8)attachments:附件,可以是图片、音频和视频,通过下拉通知显示。
  4. 指定本地通知触发条件,有 3 种触发方式:
    (1)UNTimeIntervalNotificationTrigger:一段时间后触发。
    (2)UNCalendarNotificationTrigger:指定日期时间触发。
    (3)UNLocationNotificationTrigger:根据位置触发。
  5. 根据通知内容和触发条件创建UNNotificationRequest
  6. 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 服务器建立长连接,因此不管应用是打开还是关闭的情况,都能接收到服务器推送的远程通知。

远程通知流程.png

实现原理

  1. App 打开后首先发送 UDID 和 BundleID 给 APNs 注册,并返回 deviceToken(图中步骤 1,2,3)。
  2. App 获取 deviceToken 后,通过 API 将 App 的相关信息和 deviceToken 发送给应用服务器,服务器将其记录下来。(图中步骤 4)
  3. 当要推送通知时,应用服务器按照 App 的相关信息找到存储的 deviceToken,将通知和 deviceToken 发送给 APNs。(图中步骤 5)
  4. APNs 通过 deviceToken,找到指定设备的指定 App, 并将通知推送出去。(图中步骤 6)

实现步骤

  1. 在开发者网站的 Identifiers 中添加 App IDs,并在 Capabilities 中开启 Push Notifications
  2. Certificates 中创建一个 Apple Push Notification service SSL (Sandbox & Production) 的 APNs 证书并关联第一步中的 App IDs,然后将证书下载到本地安装(安装完可以导出 P12 证书)。
  3. 在项目中选择 Capability,接着开启 Push Notifications,然后在 Background Modes 中勾选 Remote notifications
  4. 申请权限。
  5. 通过UIApplication.shared.registerForRemoteNotifications()向 APNs 请求 deviceToken。
  6. 通过func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)获取 deviceToken。如果正常获取到 deviceToken,即表示注册成功,可以进行远程通知的推送,最后需要将其发送给应用服务器。注意:
    • App 重新启动后,deviceToken 不会变化。
    • App 卸载后重新安装,deviceToken 发生变化。
  7. 通知测试。

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请求失败回调),必须在真机测试。

测试

真机测试

  1. 将 App 安装到真机上。
  2. 通过软件(如 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容