通知摘要
通知摘要是一项可选功能,允许用户停止接收特定应用程序的实时推送通知。相反,系统会存储为这些应用程序收到的通知,并在锁屏上的摘要中显示它们。系统可以在一天中的不同时刻显示这些摘要,具体取决于用户的偏好。
iOS 不会自动将应用程序添加到通知摘要中。首次启用该功能时,用户需要从系统设置中手动添加:
打开通知摘要后,推送权限提示将提供两个选项来处理应用程序的通知:
“Allow Immediate Notifications”:收到通知后立即显示。
“Add to Scheduled Summary”:收到通知后稍后显示在通知摘要中。
请注意,iOS 不提供检测用户选择的哪种通知处理方式(立即通知与预定摘要)。
新增 API
- relevanceScore 指定应在此摘要中突出显示哪些通知。
专注模式
iOS 15 的新专注模式是当前勿扰模式的改进和扩展。用户可以创建不同的“专注模式”来过滤他们在活动期间(如:工作、驾驶等)收到的通知。从系统设置中,用户可以创建“工作”焦点并将其设置在同事、家人和特定应用程序通知到达时立即通知。
专注模式激活时,其他联系人和应用程序的通知将仅显示在通知中心,如下所示:
相关 API
- UIFocusDebugger 用于调试与焦点相关交互的运行时对象。https://www.jianshu.com/p/f5c4f4b137fd
新的通知中断级别
除了新的通知摘要和专注模式之外,iOS 15 还为推送通知引入了两个新的中断级别:passive 和 time-sensitive。
总共有四个不同的中断级别:
Passive 被动:对不需要立即关注的通知使用被动模式(如:营销活动等)。被动通知不会触发声音、振动和亮屏。
Active 活动:这是默认的中断级别(如:新闻等)。
Time-Sensitive 时间敏感:对需要立即关注的通知(如:帐户安全问题、快递送达等)使用时间敏感中断级别。该中断级别不要用于发送营销通知,因为这些通知可能会突破系统控制(通知摘要和专注模式)。
Critical 严重:严重中断级别用于需要立即关注的非常重要的通知(如:恶劣天气等)。这种使用必须由 Apple 明确允许并具有特殊权利。
请注意,时间敏感和严重中断级别都可以突破通知摘要和任何专注模式。iOS 将显示刚刚收到的通知:
如果 App 的时间敏感通知不经常交互,iOS 会从锁定屏幕提示用户,让用户为 App 禁用时间敏感的通知。用户也可以从系统设置中禁用:
发送时间敏感的通知
先决条件
为了使用时间敏感通知,App 需要将“时间敏感通知”功能添加到 Xcode 项目中。
设置推送通知数据
时间敏感的中断级别可以使用“interruption-level” payload key:
{"aps":{"interruption-level":"time-sensitive"}}
新增 API
- UNNotificationInterruptionLevel 中断级别枚举。
- timeSensitiveSetting App 是否有时间敏感权限。
通知操作图标
通知操作现在可以包含图标以更好的表达相关操作。这些图标可以是 SFSymbol 系统图像,也可以是 App 提供的自定义图像。为了添加图标,Apple 提供了新的 API UNNotificationActionIcon 对象。Action Icon 对象可以使用系统或模板图像名称进行初始化,然后使用包含 icon 参数的新初始化方法添加到相应的 UNNotificationAction 中。您不需要在名称中指定文件扩展名或大小修饰符,因为会根据系统和可用图像资源自动检索正确的大小。
通讯通知
Apple 添加了将应用程序的通知区分为通信通知的功能。这些通知现在将包含发送它们的联系人的图像或头像,并且可以与 SiriKit 集成,以便 Siri 可以根据常用联系人智能地为通信操作提供快捷方式和建议。例如,当用户为焦点模式设置允许的联系人或从您的应用拨打电话时。 Siri 将根据您的应用程序提供的意图数据智能地推荐联系人。
要使用通信通知,应用程序需要在 Xcode 中向其应用程序添加 Communication Notifications 功能,并使用实现了 UNNotificationContentProviding 协议的 Intent 对象更新 App Notification Service Extension 的通知内容。目前两个可用的实现是 INSendMessageIntent 和 INStartCallIntent。
APNs 实现通讯通知
项目中添加 Notification Service Extension,将以下 key value 添加到 Notification Service Extension 的 Info.plist 中。
在 NotificationService.didReceive
中编写代码向通知对象添加附加信息。(每次 Apple APNs 在通知用户前,都会调用此方法)
import UserNotifications
import Intents
import UIKit
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
if let bestAttemptContent = bestAttemptContent {
// ...
}
}
}
需要使用 INPerson
装载发送者信息,这些信息可以通过 APNs 通知传给 App。如:
{
"aps": {
"alert": {
"body": "Hello world! This is a test message.",
"title": "@Neko"
},
},
"sender_id": "1",
"sender_name": "NekoNeko",
"sender_image_url": "https://xxxx.com/xxx.jpg",
"sender_nickname": "@Neko",
"sender_email": "Neko@Neko.Neko",
"chat_session_id": "chat_1"
}
然后,可以通过使用 INPerson
和 INSendMessageIntent
将发送者信息添加到推送通知中。
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
/* Use the custom information included in this notification from the chat server to retrive the chat participant's information. - This is the information of the sender of the message. - Providing a user's accruate name helps the iOS system match this user with a contact in the system contact app. */
if let senderAccountID = bestAttemptContent.userInfo["sender_id"] as? String,
let senderName = bestAttemptContent.userInfo["sender_name"] as? String,
let senderImageURLString = bestAttemptContent.userInfo["sender_image_url"] as? String,
let senderImageURL = URL(string: senderImageURLString),
let senderDisplayName = bestAttemptContent.userInfo["sender_nickname"] as? String,
let senderEmailAddr = bestAttemptContent.userInfo["sender_email"] as? String,
let chatSessionID = bestAttemptContent.userInfo["chat_session_id"] as? String
{
// You can also use the sender's phone number to initialize the `INPersonHandle` object. This will help the iOS system to match this sender with a contact.
// TODO: - Here you need to download the image data from the URL. In this demo, we are using a system image instead.
let messageSender = INPerson(
// email or phone number
personHandle: INPersonHandle(value: senderEmailAddr, type: .emailAddress),
nameComponents: try? PersonNameComponents(senderName),
displayName: senderDisplayName,
image: INImage(imageData: UIImage(systemName: "applelogo")!.pngData()!),
contactIdentifier: nil,
customIdentifier: senderAccountID,
isMe: false,
suggestionType: .instantMessageAddress
)
let intent = INSendMessageIntent(recipients: nil,
outgoingMessageType: .outgoingMessageText,
content: bestAttemptContent.body,
speakableGroupName: INSpeakableString(spokenPhrase: senderDisplayName),
conversationIdentifier: chatSessionID,
serviceName: nil,
sender: messageSender,
attachments: nil)
let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .incoming
interaction.donate(completion: nil)
do {
let messageContent = try request.content.updating(from: intent)
contentHandler(messageContent)
} catch {
print(error.localizedDescription)
}
}
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}