MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift UserNotifications用户通知

2023-04-132.1k 阅读

Swift UserNotifications 用户通知基础概念

在 iOS、iPadOS、macOS、watchOS 和 tvOS 应用开发中,用户通知(User Notifications)是一种强大的功能,它允许应用在适当的时候向用户推送信息,即便应用当前不在前台运行。Swift 的 UserNotifications 框架为开发者提供了一套丰富且灵活的 API 来管理和呈现这些通知。

通知的类型

  1. 本地通知(Local Notifications):这类通知由设备上的应用直接生成并发送。例如,日历应用可以在事件即将开始时发送本地通知提醒用户。本地通知不需要网络连接,完全由设备端触发。
  2. 远程通知(Remote Notifications):通过服务器发送到用户设备的通知。像社交媒体应用,当有新消息、点赞或评论时,服务器会向用户设备推送远程通知。远程通知依赖于网络连接和应用的推送服务配置。

通知的组成部分

  1. 标题(Title):简短地概括通知的主题,吸引用户注意力。
  2. 正文(Body):详细描述通知的具体内容,为用户提供更多信息。
  3. 附件(Attachment):可以是图片、视频或音频等媒体文件,丰富通知的展示形式。例如,图片分享应用可以在通知中附带新上传图片的预览。
  4. 操作(Actions):允许用户直接在通知上执行某些操作,如回复消息、删除事项等,无需打开应用。

配置 UserNotifications 框架

在使用 UserNotifications 框架之前,需要在项目中进行相应的配置。

导入框架

在 Swift 文件的开头,使用 import 关键字导入 UserNotifications 框架:

import UserNotifications

请求授权

为了向用户发送通知,应用首先需要获得用户的授权。在 iOS 中,可以在应用启动时请求授权,示例代码如下:

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
    if granted {
        print("Notification authorization granted.")
    } else if let error = error {
        print("Notification authorization failed: \(error)")
    }
}

在上述代码中,requestAuthorization(options:completionHandler:) 方法用于请求授权,options 参数指定了应用希望获得的通知权限,这里包括显示提醒、播放声音和设置应用图标徽章数。completionHandler 是授权请求完成后的回调,granted 参数表示用户是否授权,error 参数如果存在则表示授权过程中出现的错误。

获取当前授权状态

有时候,需要检查当前应用的通知授权状态,可以使用以下代码:

UNUserNotificationCenter.current().getNotificationSettings { (settings) in
    switch settings.authorizationStatus {
    case .authorized:
        print("Notification authorization is authorized.")
    case .denied:
        print("Notification authorization is denied.")
    case .notDetermined:
        print("Notification authorization is not determined yet.")
    case .provisional:
        print("Notification authorization is provisional.")
    @unknown default:
        print("Unknown authorization status.")
    }
}

getNotificationSettings(completionHandler:) 方法会在完成后调用 completionHandler,通过 settings.authorizationStatus 可以获取当前的授权状态。.authorized 表示用户已授权;.denied 表示用户拒绝授权;.notDetermined 表示用户尚未做出决定;.provisional 是 iOS 12 引入的临时授权状态,应用可以在这种状态下显示有限形式的通知。

创建和发送本地通知

创建 UNMutableNotificationContent

UNMutableNotificationContent 类用于配置通知的内容,包括标题、正文、声音、徽章等。以下是一个简单的示例:

let content = UNMutableNotificationContent()
content.title = "新消息"
content.body = "你收到了一条新消息,请查看。"
content.sound = UNNotificationSound.default
content.badge = 1

在上述代码中,首先创建了一个 UNMutableNotificationContent 实例 content。然后设置了通知的标题为“新消息”,正文为“你收到了一条新消息,请查看。”,声音使用默认声音,徽章数设置为 1。

创建 UNNotificationRequest

UNNotificationRequest 类用于创建通知请求,它关联了 UNMutableNotificationContent 和触发通知的条件。以下是一个基于时间触发的通知请求示例:

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: "newMessageNotification", content: content, trigger: trigger)

在上述代码中,UNTimeIntervalNotificationTrigger 用于创建一个基于时间间隔触发的通知触发器,这里设置为 5 秒后触发,并且不重复。UNNotificationRequest 的构造函数接收三个参数:identifier 是通知的唯一标识符,用于管理和取消特定通知;content 是之前创建的 UNMutableNotificationContent 实例;trigger 是通知的触发条件。

添加通知请求到 UNUserNotificationCenter

创建好通知请求后,需要将其添加到 UNUserNotificationCenter 中,以便系统在合适的时机发送通知:

UNUserNotificationCenter.current().add(request) { (error) in
    if let error = error {
        print("Failed to add notification request: \(error)")
    }
}

add(_:completionHandler:) 方法将通知请求添加到通知中心,并在完成后调用 completionHandler。如果添加过程中出现错误,error 参数将包含错误信息。

通知的触发条件

基于时间的触发

除了前面示例中的 UNTimeIntervalNotificationTrigger,还可以使用 UNCalendarNotificationTrigger 基于日历时间触发通知。例如,每天早上 8 点发送通知:

var dateComponents = DateComponents()
dateComponents.hour = 8
dateComponents.minute = 0
dateComponents.second = 0
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let content = UNMutableNotificationContent()
content.title = "早安"
content.body = "美好的一天开始啦!"
content.sound = UNNotificationSound.default
let request = UNNotificationRequest(identifier: "morningNotification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
    if let error = error {
        print("Failed to add notification request: \(error)")
    }
}

在上述代码中,首先创建了 DateComponents 实例,设置小时为 8,分钟和秒为 0。然后使用 UNCalendarNotificationTrigger 根据这些日历组件创建触发器,并设置为重复触发。接着创建通知内容和请求,并添加到通知中心。

基于位置的触发

UNLocationNotificationTrigger 可用于基于用户位置触发通知。例如,当用户进入某个特定地理区域时发送通知:

import CoreLocation

let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), radius: 100, identifier: "myLocationRegion")
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)
let content = UNMutableNotificationContent()
content.title = "欢迎来到这里"
content.body = "你已到达指定地点。"
content.sound = UNNotificationSound.default
let request = UNNotificationRequest(identifier: "locationNotification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
    if let error = error {
        print("Failed to add notification request: \(error)")
    }
}

在上述代码中,首先导入了 CoreLocation 框架,然后创建了一个圆形地理区域 region,中心坐标为 (37.7749, -122.4194),半径为 100 米,并指定了标识符。接着使用 UNLocationNotificationTrigger 根据该区域创建触发器,设置为不重复触发。之后创建通知内容和请求,并添加到通知中心。需要注意的是,使用基于位置的触发需要用户授予应用位置权限。

处理通知交互

处理通知点击

当用户点击通知时,可以在应用中进行相应的处理。在 iOS 应用的 AppDelegate 中,可以实现以下方法:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    let identifier = response.notification.request.identifier
    if identifier == "newMessageNotification" {
        // 处理新消息通知点击逻辑
        print("用户点击了新消息通知")
    }
    completionHandler()
}

在上述代码中,userNotificationCenter(_:didReceive:withCompletionHandler:) 方法在用户点击通知时被调用。通过 response.notification.request.identifier 可以获取通知的标识符,根据标识符进行相应的处理逻辑。处理完成后,需要调用 completionHandler

自定义通知操作

除了默认的通知点击操作,还可以为通知添加自定义操作。例如,为消息通知添加“回复”和“删除”操作:

let replyAction = UNTextInputNotificationAction(identifier: "replyAction", title: "回复", options: [], textInputButtonTitle: "发送", textInputPlaceholder: "输入回复内容")
let deleteAction = UNNotificationAction(identifier: "deleteAction", title: "删除", options: .destructive)
let category = UNNotificationCategory(identifier: "messageCategory", actions: [replyAction, deleteAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])

在上述代码中,首先创建了一个 UNTextInputNotificationAction 类型的“回复”操作 replyAction,它允许用户输入文本。identifier 是操作的唯一标识符,title 是显示在通知上的标题,textInputButtonTitle 是输入框的发送按钮标题,textInputPlaceholder 是输入框的占位文本。然后创建了一个普通的 UNNotificationAction 类型的“删除”操作 deleteAction,设置 options.destructive 以显示为红色的删除样式。接着创建了一个 UNNotificationCategory 实例 category,将“回复”和“删除”操作添加到该类别中。最后,通过 UNUserNotificationCenter.current().setNotificationCategories(_:) 方法将该类别设置到通知中心。

AppDelegate 中处理自定义操作:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    let identifier = response.actionIdentifier
    if identifier == "replyAction" {
        if let text = response.textInput {
            // 处理回复内容
            print("用户回复内容:\(text)")
        }
    } else if identifier == "deleteAction" {
        // 处理删除逻辑
        print("用户点击了删除操作")
    }
    completionHandler()
}

在上述代码中,根据 response.actionIdentifier 判断用户点击的是哪个操作。如果是“回复”操作,通过 response.textInput 获取用户输入的文本;如果是“删除”操作,则执行相应的删除逻辑。

远程通知

远程通知的工作原理

远程通知通过苹果的推送通知服务(APNs)来实现。应用服务器将通知发送到 APNs,APNs 再将通知推送到用户设备上的应用。应用在启动时需要向系统注册远程通知,获取设备令牌(Device Token),并将设备令牌发送到应用服务器。服务器使用设备令牌来标识特定的设备,并向其发送远程通知。

注册远程通知

在 iOS 应用的 AppDelegate 中,可以注册远程通知:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    if #available(iOS 10.0, *) {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            } else if let error = error {
                print("Notification authorization failed: \(error)")
            }
        }
    } else {
        let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
        application.registerUserNotificationSettings(settings)
        application.registerForRemoteNotifications()
    }
    return true
}

在上述代码中,首先检查当前系统版本是否支持 iOS 10.0 及以上。如果支持,使用 UNUserNotificationCenter 请求通知授权,授权成功后在主线程中调用 application.registerForRemoteNotifications() 注册远程通知。如果系统版本低于 iOS 10.0,则使用旧的 UIUserNotificationSettings 方式请求授权并注册远程通知。

接收设备令牌

当应用成功注册远程通知后,系统会回调 AppDelegate 中的 application(_:didRegisterForRemoteNotificationsWithDeviceToken:) 方法,在该方法中可以获取设备令牌:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
    let token = tokenParts.joined()
    print("Device Token: \(token)")
    // 将设备令牌发送到应用服务器
}

在上述代码中,将 deviceToken 转换为十六进制字符串形式,并打印出来。通常,需要将这个设备令牌发送到应用服务器,以便服务器向该设备发送远程通知。

处理远程通知接收

当应用接收到远程通知时,会调用 AppDelegate 中的相应方法。在 iOS 10.0 及以上,可以使用 UNUserNotificationCenterDelegate 的方法来处理:

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    completionHandler([.alert, .sound, .badge])
}

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    // 处理远程通知中的用户信息
    print("Remote notification userInfo: \(userInfo)")
    completionHandler()
}

userNotificationCenter(_:willPresent:withCompletionHandler:) 方法中,如果应用在前台运行,通过 completionHandler 设置通知的展示选项,这里设置为显示提醒、播放声音和设置徽章。在 userNotificationCenter(_:didReceive:withCompletionHandler:) 方法中,通过 response.notification.request.content.userInfo 获取远程通知中的用户信息,并进行相应的处理。

通知附件

添加通知附件

通知附件可以是图片、视频或音频等文件。以下是添加图片附件的示例:

let fileURL = Bundle.main.url(forResource: "example", withExtension: "jpg")
do {
    let attachment = try UNNotificationAttachment(identifier: "imageAttachment", url: fileURL!, options: nil)
    let content = UNMutableNotificationContent()
    content.title = "图片通知"
    content.body = "这里有一张新图片。"
    content.attachments = [attachment]
    content.sound = UNNotificationSound.default
    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
    let request = UNNotificationRequest(identifier: "imageNotification", content: content, trigger: trigger)
    UNUserNotificationCenter.current().add(request) { (error) in
        if let error = error {
            print("Failed to add notification request: \(error)")
        }
    }
} catch {
    print("Failed to create attachment: \(error)")
}

在上述代码中,首先获取应用主 bundle 中的图片文件 URL。然后使用 UNNotificationAttachment 的构造函数创建附件,identifier 是附件的唯一标识符,url 是附件文件的 URL。接着创建通知内容,将附件添加到 attachments 数组中。之后创建触发器、通知请求,并添加到通知中心。如果创建附件过程中出现错误,会捕获并打印错误信息。

显示通知附件

当通知包含附件时,系统会根据附件类型自动以合适的方式显示。对于图片附件,会在通知中显示图片预览;对于视频和音频附件,会显示相应的播放按钮。用户可以点击附件进行查看或播放。

通知的管理和调试

取消通知

可以根据通知的标识符取消特定的通知。例如,取消之前创建的“newMessageNotification”通知:

UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["newMessageNotification"])

removePendingNotificationRequests(withIdentifiers:) 方法会取消所有标识符在指定数组中的未发送通知请求。

清除所有通知

如果需要清除设备上应用的所有通知,可以使用以下方法:

UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().removeAllDeliveredNotifications()

removeAllPendingNotificationRequests() 方法会取消所有未发送的通知请求,removeAllDeliveredNotifications() 方法会清除设备上已经显示过的所有通知。

调试通知

在开发过程中,可以通过 Xcode 的调试功能来查看通知相关的日志。在设备控制台中,可以查看通知授权、发送、接收等操作的详细信息。此外,还可以使用 print 语句在代码中输出关键步骤的信息,以便定位问题。例如,在通知请求添加失败时打印错误信息,有助于快速发现和解决问题。同时,对于远程通知,可以在服务器端记录发送日志,与客户端的接收情况进行对比调试。

通过以上对 Swift UserNotifications 框架的详细介绍,开发者可以全面掌握如何在应用中有效地管理和利用用户通知功能,为用户提供更好的交互体验。无论是本地通知还是远程通知,都能根据应用的需求进行灵活配置和处理,满足不同场景下的通知需求。