push-notifications
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePush Notifications
推送通知
Implement, review, and debug local and remote notifications on iOS/macOS using and APNs. Covers permission flow, token registration, payload structure, foreground handling, notification actions, grouping, and rich notifications. Targets iOS 26+ with Swift 6.2, backward-compatible to iOS 16 unless noted.
UserNotifications使用和APNs在iOS/macOS上实现、审核和调试本地与远程通知。内容涵盖权限流程、令牌注册、负载结构、前台处理、通知操作、分组以及富通知。目标平台为iOS 26+,使用Swift 6.2,除非特别说明,否则向下兼容至iOS 16。
UserNotificationsPermission Flow
权限流程
Request notification authorization before doing anything else. The system prompt appears only once; subsequent calls return the stored decision.
swift
import UserNotifications
@MainActor
func requestNotificationPermission() async -> Bool {
let center = UNUserNotificationCenter.current()
do {
let granted = try await center.requestAuthorization(
options: [.alert, .sound, .badge]
)
return granted
} catch {
print("Authorization request failed: \(error)")
return false
}
}在进行任何操作前先请求通知授权。系统提示仅会显示一次;后续调用将返回已存储的用户决策。
swift
import UserNotifications
@MainActor
func requestNotificationPermission() async -> Bool {
let center = UNUserNotificationCenter.current()
do {
let granted = try await center.requestAuthorization(
options: [.alert, .sound, .badge]
)
return granted
} catch {
print("Authorization request failed: \(error)")
return false
}
}Checking Current Status
检查当前状态
Always check status before assuming permissions. The user can change settings at any time.
swift
@MainActor
func checkNotificationStatus() async -> UNAuthorizationStatus {
let settings = await UNUserNotificationCenter.current().notificationSettings()
return settings.authorizationStatus
// .notDetermined, .denied, .authorized, .provisional, .ephemeral
}永远不要假设权限已开启,操作前务必检查状态。用户可随时更改设置。
swift
@MainActor
func checkNotificationStatus() async -> UNAuthorizationStatus {
let settings = await UNUserNotificationCenter.current().notificationSettings()
return settings.authorizationStatus
// .notDetermined, .denied, .authorized, .provisional, .ephemeral
}Provisional Notifications
临时通知
Provisional notifications deliver quietly to the notification center without interrupting the user. The user can then choose to keep or turn them off. Use for onboarding flows where you want to demonstrate value before asking for full permission.
swift
// Delivers silently -- no permission prompt shown to the user
try await center.requestAuthorization(options: [.alert, .sound, .badge, .provisional])临时通知会静默投递至通知中心,不会打扰用户。之后用户可选择保留或关闭此类通知。适用于引导流程,可在请求完整权限前向用户展示通知价值。
swift
// 静默投递——不会向用户显示权限提示
try await center.requestAuthorization(options: [.alert, .sound, .badge, .provisional])Critical Alerts
关键提醒
Critical alerts bypass Do Not Disturb and the mute switch. Requires a special entitlement from Apple (request via developer portal). Use only for health, safety, or security scenarios.
swift
// Requires com.apple.developer.usernotifications.critical-alerts entitlement
try await center.requestAuthorization(
options: [.alert, .sound, .badge, .criticalAlert]
)关键提醒可绕过“勿扰模式”和静音开关。需要向Apple申请特殊权限(通过开发者门户提交请求)。仅可用于健康、安全或安保场景。
swift
// 需要com.apple.developer.usernotifications.critical-alerts权限
try await center.requestAuthorization(
options: [.alert, .sound, .badge, .criticalAlert]
)Handling Denied Permissions
处理权限被拒的情况
When the user has denied notifications, guide them to Settings. Do not repeatedly prompt or nag.
swift
@MainActor
func openNotificationSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
}
// Usage: show a button/banner explaining why notifications matter,
// then call openNotificationSettings() on tap.当用户拒绝通知权限时,引导他们前往设置。不要反复提示或骚扰用户。
swift
@MainActor
func openNotificationSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
}
// 使用方式:展示按钮/横幅说明通知的重要性,
// 点击时调用openNotificationSettings()。APNs Registration
APNs注册
Use to receive the device token in a SwiftUI app. The AppDelegate callbacks are the only way to receive APNs tokens.
UIApplicationDelegateAdaptorswift
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
return true
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02x", $0) }.joined()
print("APNs token: \(token)")
// Send token to your server
Task { await TokenService.shared.upload(token: token) }
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("APNs registration failed: \(error.localizedDescription)")
// Simulator always fails -- this is expected during development
}
}在SwiftUI应用中使用接收设备令牌。AppDelegate回调是接收APNs令牌的唯一方式。
UIApplicationDelegateAdaptorswift
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
return true
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02x", $0) }.joined()
print("APNs token: \(token)")
// 将令牌发送至你的服务器
Task { await TokenService.shared.upload(token: token) }
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("APNs registration failed: \(error.localizedDescription)")
// 模拟器中注册总会失败——这属于开发期间的正常情况
}
}Registration Order
注册顺序
Request authorization first, then register for remote notifications. Registration triggers the system to contact APNs and return a device token.
swift
@MainActor
func registerForPush() async {
let granted = await requestNotificationPermission()
guard granted else { return }
UIApplication.shared.registerForRemoteNotifications()
}先请求授权,再注册远程通知。注册操作会触发系统联系APNs并返回设备令牌。
swift
@MainActor
func registerForPush() async {
let granted = await requestNotificationPermission()
guard granted else { return }
UIApplication.shared.registerForRemoteNotifications()
}Token Handling
令牌处理
Device tokens change. Re-send the token to your server every time fires, not just the first time. The system calls this method on every app launch that calls .
didRegisterForRemoteNotificationsWithDeviceTokenregisterForRemoteNotifications()设备令牌会发生变化。每次触发时,都要重新将令牌发送至服务器,而不只是第一次。每次调用的应用启动时,系统都会调用该方法。
didRegisterForRemoteNotificationsWithDeviceTokenregisterForRemoteNotifications()Local Notifications
本地通知
Schedule notifications directly from the device without a server. Useful for reminders, timers, and location-based alerts.
直接在设备上调度通知,无需服务器。适用于提醒、计时器和基于位置的警报。
Creating Content
创建通知内容
swift
let content = UNMutableNotificationContent()
content.title = "Workout Reminder"
content.subtitle = "Time to move"
content.body = "You have a scheduled workout in 15 minutes."
content.sound = .default
content.badge = 1
content.userInfo = ["workoutId": "abc123"]
content.threadIdentifier = "workouts" // groups in notification centerswift
let content = UNMutableNotificationContent()
content.title = "Workout Reminder"
content.subtitle = "Time to move"
content.body = "You have a scheduled workout in 15 minutes."
content.sound = .default
content.badge = 1
content.userInfo = ["workoutId": "abc123"]
content.threadIdentifier = "workouts" // 在通知中心分组Trigger Types
触发器类型
swift
// Fire after a time interval (minimum 60 seconds for repeating)
let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 300, repeats: false)
// Fire at a specific date/time
var dateComponents = DateComponents()
dateComponents.hour = 8
dateComponents.minute = 30
let calendarTrigger = UNCalendarNotificationTrigger(
dateMatching: dateComponents, repeats: true // daily at 8:30 AM
)
// Fire when entering a geographic region
let region = CLCircularRegion(
center: CLLocationCoordinate2D(latitude: 37.33, longitude: -122.01),
radius: 100,
identifier: "gym"
)
region.notifyOnEntry = true
region.notifyOnExit = false
let locationTrigger = UNLocationNotificationTrigger(region: region, repeats: false)
// Requires "When In Use" location permission at minimumswift
// 时间间隔后触发(重复触发的最小间隔为60秒)
let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 300, repeats: false)
// 在特定日期/时间触发
var dateComponents = DateComponents()
dateComponents.hour = 8
dateComponents.minute = 30
let calendarTrigger = UNCalendarNotificationTrigger(
dateMatching: dateComponents, repeats: true // 每天早上8:30触发
)
// 进入地理区域时触发
let region = CLCircularRegion(
center: CLLocationCoordinate2D(latitude: 37.33, longitude: -122.01),
radius: 100,
identifier: "gym"
)
region.notifyOnEntry = true
region.notifyOnExit = false
let locationTrigger = UNLocationNotificationTrigger(region: region, repeats: false)
// 至少需要“使用期间”位置权限Scheduling and Managing
调度与管理
swift
let request = UNNotificationRequest(
identifier: "workout-reminder-abc123",
content: content,
trigger: timeTrigger
)
let center = UNUserNotificationCenter.current()
try await center.add(request)
// Remove specific pending notifications
center.removePendingNotificationRequests(withIdentifiers: ["workout-reminder-abc123"])
// Remove all pending
center.removeAllPendingNotificationRequests()
// Remove delivered notifications from notification center
center.removeDeliveredNotifications(withIdentifiers: ["workout-reminder-abc123"])
center.removeAllDeliveredNotifications()
// List all pending requests
let pending = await center.pendingNotificationRequests()swift
let request = UNNotificationRequest(
identifier: "workout-reminder-abc123",
content: content,
trigger: timeTrigger
)
let center = UNUserNotificationCenter.current()
try await center.add(request)
// 移除特定的待处理通知
center.removePendingNotificationRequests(withIdentifiers: ["workout-reminder-abc123"])
// 移除所有待处理通知
center.removeAllPendingNotificationRequests()
// 从通知中心移除已投递的通知
center.removeDeliveredNotifications(withIdentifiers: ["workout-reminder-abc123"])
center.removeAllDeliveredNotifications()
// 列出所有待处理请求
let pending = await center.pendingNotificationRequests()Remote Notification Payload
远程通知负载
Standard APNs Payload
标准APNs负载
json
{
"aps": {
"alert": {
"title": "New Message",
"subtitle": "From Alice",
"body": "Hey, are you free for lunch?"
},
"badge": 3,
"sound": "default",
"thread-id": "chat-alice",
"category": "MESSAGE_CATEGORY"
},
"messageId": "msg-789",
"senderId": "user-alice"
}json
{
"aps": {
"alert": {
"title": "New Message",
"subtitle": "From Alice",
"body": "Hey, are you free for lunch?"
},
"badge": 3,
"sound": "default",
"thread-id": "chat-alice",
"category": "MESSAGE_CATEGORY"
},
"messageId": "msg-789",
"senderId": "user-alice"
}Silent / Background Push
静默/后台推送
Set with no alert, sound, or badge. The system wakes the app in the background. Requires the "Background Modes > Remote notifications" capability.
content-available: 1json
{
"aps": {
"content-available": 1
},
"updateType": "new-data"
}Handle in AppDelegate:
swift
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any]
) async -> UIBackgroundFetchResult {
guard let updateType = userInfo["updateType"] as? String else {
return .noData
}
do {
try await DataSyncService.shared.sync(trigger: updateType)
return .newData
} catch {
return .failed
}
}设置,且不包含提醒、声音或角标。系统会在后台唤醒应用。需要开启“后台模式>远程通知”权限。
content-available: 1json
{
"aps": {
"content-available": 1
},
"updateType": "new-data"
}在AppDelegate中处理:
swift
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any]
) async -> UIBackgroundFetchResult {
guard let updateType = userInfo["updateType"] as? String else {
return .noData
}
do {
try await DataSyncService.shared.sync(trigger: updateType)
return .newData
} catch {
return .failed
}
}Mutable Content
可变内容
Set to allow a Notification Service Extension to modify content before display. Use for downloading images, decrypting content, or adding attachments.
mutable-content: 1json
{
"aps": {
"alert": { "title": "Photo", "body": "Alice sent a photo" },
"mutable-content": 1
},
"imageUrl": "https://example.com/photo.jpg"
}设置,允许通知服务扩展在展示前修改内容。可用于下载图片、解密内容或添加附件。
mutable-content: 1json
{
"aps": {
"alert": { "title": "Photo", "body": "Alice sent a photo" },
"mutable-content": 1
},
"imageUrl": "https://example.com/photo.jpg"
}Localized Notifications
本地化通知
Use localization keys so the notification displays in the user's language:
json
{
"aps": {
"alert": {
"title-loc-key": "NEW_MESSAGE_TITLE",
"loc-key": "NEW_MESSAGE_BODY",
"loc-args": ["Alice"]
}
}
}使用本地化键,让通知以用户的语言显示:
json
{
"aps": {
"alert": {
"title-loc-key": "NEW_MESSAGE_TITLE",
"loc-key": "NEW_MESSAGE_BODY",
"loc-args": ["Alice"]
}
}
}Notification Handling
通知处理
UNUserNotificationCenterDelegate
UNUserNotificationCenterDelegate
Implement the delegate to control foreground display and handle user taps. Set the delegate as early as possible -- in or .
application(_:didFinishLaunchingWithOptions:)App.initswift
@MainActor
final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, Sendable {
static let shared = NotificationDelegate()
// Called when notification arrives while app is in FOREGROUND
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification
) async -> UNNotificationPresentationOptions {
// Return which presentation elements to show
// Without this, foreground notifications are silently suppressed
return [.banner, .sound, .badge]
}
// Called when user TAPS the notification
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse
) async {
let userInfo = response.notification.request.content.userInfo
let actionIdentifier = response.actionIdentifier
switch actionIdentifier {
case UNNotificationDefaultActionIdentifier:
// User tapped the notification body
await handleNotificationTap(userInfo: userInfo)
case UNNotificationDismissActionIdentifier:
// User dismissed the notification
break
default:
// Custom action button tapped
await handleCustomAction(actionIdentifier, userInfo: userInfo)
}
}
}实现代理以控制前台展示并处理用户点击。尽早设置代理——在或中。
application(_:didFinishLaunchingWithOptions:)App.initswift
@MainActor
final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, Sendable {
static let shared = NotificationDelegate()
// 应用在前台时收到通知时调用
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification
) async -> UNNotificationPresentationOptions {
// 返回要展示的元素
// 不实现此方法的话,前台通知会被静默抑制
return [.banner, .sound, .badge]
}
// 用户点击通知时调用
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse
) async {
let userInfo = response.notification.request.content.userInfo
let actionIdentifier = response.actionIdentifier
switch actionIdentifier {
case UNNotificationDefaultActionIdentifier:
// 用户点击了通知主体
await handleNotificationTap(userInfo: userInfo)
case UNNotificationDismissActionIdentifier:
// 用户 dismissed 通知
break
default:
// 用户点击了自定义操作按钮
await handleCustomAction(actionIdentifier, userInfo: userInfo)
}
}
}Deep Linking from Notifications
从通知跳转至深层链接
Route notification taps to the correct screen using a shared router. The delegate writes a pending destination; the SwiftUI view observes and consumes it.
@Observableswift
@Observable @MainActor
final class DeepLinkRouter {
var pendingDestination: AppDestination?
}
// In NotificationDelegate:
func handleNotificationTap(userInfo: [AnyHashable: Any]) async {
guard let id = userInfo["messageId"] as? String else { return }
DeepLinkRouter.shared.pendingDestination = .chat(id: id)
}
// In SwiftUI -- observe and consume:
.onChange(of: router.pendingDestination) { _, destination in
if let destination {
path.append(destination)
router.pendingDestination = nil
}
}See for the full deep-linking handler with tab switching.
references/notification-patterns.md使用共享的路由器将通知点击路由至正确的屏幕。代理写入待处理的目标,SwiftUI视图监听并处理该目标。
@Observableswift
@Observable @MainActor
final class DeepLinkRouter {
var pendingDestination: AppDestination?
}
// 在NotificationDelegate中:
func handleNotificationTap(userInfo: [AnyHashable: Any]) async {
guard let id = userInfo["messageId"] as? String else { return }
DeepLinkRouter.shared.pendingDestination = .chat(id: id)
}
// 在SwiftUI中——监听并处理:
.onChange(of: router.pendingDestination) { _, destination in
if let destination {
path.append(destination)
router.pendingDestination = nil
}
}查看获取完整的深层链接处理逻辑,包括标签页切换。
references/notification-patterns.mdNotification Actions and Categories
通知操作与类别
Define interactive actions that appear as buttons on the notification. Register categories at launch.
定义显示为通知按钮的交互式操作。在应用启动时注册类别。
Defining Categories and Actions
定义类别与操作
swift
func registerNotificationCategories() {
let replyAction = UNTextInputNotificationAction(
identifier: "REPLY_ACTION",
title: "Reply",
options: [],
textInputButtonTitle: "Send",
textInputPlaceholder: "Type a reply..."
)
let likeAction = UNNotificationAction(
identifier: "LIKE_ACTION",
title: "Like",
options: []
)
let deleteAction = UNNotificationAction(
identifier: "DELETE_ACTION",
title: "Delete",
options: [.destructive, .authenticationRequired]
)
let messageCategory = UNNotificationCategory(
identifier: "MESSAGE_CATEGORY",
actions: [replyAction, likeAction, deleteAction],
intentIdentifiers: [],
options: [.customDismissAction] // fires didReceive on dismiss too
)
UNUserNotificationCenter.current().setNotificationCategories([messageCategory])
}swift
func registerNotificationCategories() {
let replyAction = UNTextInputNotificationAction(
identifier: "REPLY_ACTION",
title: "Reply",
options: [],
textInputButtonTitle: "Send",
textInputPlaceholder: "Type a reply..."
)
let likeAction = UNNotificationAction(
identifier: "LIKE_ACTION",
title: "Like",
options: []
)
let deleteAction = UNNotificationAction(
identifier: "DELETE_ACTION",
title: "Delete",
options: [.destructive, .authenticationRequired]
)
let messageCategory = UNNotificationCategory(
identifier: "MESSAGE_CATEGORY",
actions: [replyAction, likeAction, deleteAction],
intentIdentifiers: [],
options: [.customDismissAction] // Dismiss时也会触发didReceive
)
UNUserNotificationCenter.current().setNotificationCategories([messageCategory])
}Handling Action Responses
处理操作响应
swift
func handleCustomAction(_ identifier: String, userInfo: [AnyHashable: Any]) async {
switch identifier {
case "REPLY_ACTION":
// response is UNTextInputNotificationResponse for text input actions
break
case "LIKE_ACTION":
guard let messageId = userInfo["messageId"] as? String else { return }
await MessageService.shared.likeMessage(id: messageId)
case "DELETE_ACTION":
guard let messageId = userInfo["messageId"] as? String else { return }
await MessageService.shared.deleteMessage(id: messageId)
default:
break
}
}Action options:
- -- device must be unlocked to perform the action
.authenticationRequired - -- displayed in red; use for delete/remove actions
.destructive - -- launches the app to the foreground when tapped
.foreground
swift
func handleCustomAction(_ identifier: String, userInfo: [AnyHashable: Any]) async {
switch identifier {
case "REPLY_ACTION":
// 对于文本输入操作,response是UNTextInputNotificationResponse
break
case "LIKE_ACTION":
guard let messageId = userInfo["messageId"] as? String else { return }
await MessageService.shared.likeMessage(id: messageId)
case "DELETE_ACTION":
guard let messageId = userInfo["messageId"] as? String else { return }
await MessageService.shared.deleteMessage(id: messageId)
default:
break
}
}操作选项:
- —— 执行操作前必须解锁设备
.authenticationRequired - —— 红色显示;用于删除/移除操作
.destructive - —— 点击时将应用启动至前台
.foreground
Notification Grouping
通知分组
Group related notifications with (or in the APNs payload). Each unique thread becomes a separate group in Notification Center.
threadIdentifierthread-idswift
content.threadIdentifier = "chat-alice" // all messages from Alice group together
content.summaryArgument = "Alice"
content.summaryArgumentCount = 3 // "3 more notifications from Alice"Customize the summary format string in the category:
swift
let category = UNNotificationCategory(
identifier: "MESSAGE_CATEGORY",
actions: [replyAction],
intentIdentifiers: [],
categorySummaryFormat: "%u more messages from %@",
options: []
)使用(或APNs负载中的)对相关通知进行分组。每个唯一的线程会在通知中心形成一个独立的分组。
threadIdentifierthread-idswift
content.threadIdentifier = "chat-alice" // 来自Alice的所有消息会分组在一起
content.summaryArgument = "Alice"
content.summaryArgumentCount = 3 // “来自Alice的3条更多通知”在类别中自定义摘要格式字符串:
swift
let category = UNNotificationCategory(
identifier: "MESSAGE_CATEGORY",
actions: [replyAction],
intentIdentifiers: [],
categorySummaryFormat: "%u more messages from %@",
options: []
)Common Mistakes
常见错误
DON'T: Register for remote notifications before requesting authorization.
DO: Call first, check the result, then call .
requestAuthorizationregisterForRemoteNotifications()DON'T: Convert the device token with -- this produces garbage or nil.
DO: Convert token bytes to a hex string: .
String(data: deviceToken, encoding: .utf8)deviceToken.map { String(format: "%02x", $0) }.joined()DON'T: Assume notifications always arrive. APNs is best-effort delivery; the system may throttle or drop notifications.
DO: Design features that degrade gracefully without notifications. Use background refresh as a fallback.
DON'T: Put sensitive data directly in the notification payload. It is visible on the lock screen and in notification center.
DO: Use with a Notification Service Extension to fetch sensitive content from your server.
mutable-content: 1DON'T: Forget to handle the foreground case. Without , notifications are silently suppressed when the app is active.
DO: Implement and return the desired presentation options (, , ).
willPresentwillPresent.banner.sound.badgeDON'T: Set too late (e.g., in a view's ).
DO: Set the delegate in , , or the AppDelegate adaptor.
UNUserNotificationCenter.current().delegate.onAppearApp.initapplication(_:didFinishLaunchingWithOptions:)DON'T: Call from a SwiftUI view without an AppDelegate adaptor. There is no SwiftUI-native way to receive the token callback.
DO: Use for all APNs registration and token handling.
UIApplication.shared.registerForRemoteNotifications()UIApplicationDelegateAdaptorDON'T: Ignore . Log it so device failures surface. On simulator, fail silently.
didFailToRegisterForRemoteNotificationsWithErrorDON'T: Send the device token to your server only once. Tokens change periodically.
DO: Re-send on every call.
didRegisterForRemoteNotificationsWithDeviceToken不要: 在请求授权前注册远程通知。
要: 先调用,检查结果,再调用。
requestAuthorizationregisterForRemoteNotifications()不要: 使用转换设备令牌——这会产生无效内容或nil。
要: 将令牌字节转换为十六进制字符串:。
String(data: deviceToken, encoding: .utf8)deviceToken.map { String(format: "%02x", $0) }.joined()不要: 假设通知总会投递成功。APNs是尽力而为的投递机制;系统可能会限流或丢弃通知。
要: 设计无通知时也能正常降级的功能。使用后台刷新作为备选方案。
不要: 将敏感数据直接放入通知负载中。它会在锁屏和通知中心显示。
要: 使用搭配通知服务扩展从服务器获取敏感内容。
mutable-content: 1不要: 忘记处理前台场景。没有的话,应用在活跃状态时通知会被静默抑制。
要: 实现并返回所需的展示选项(, , )。
willPresentwillPresent.banner.sound.badge不要: 太晚设置(例如在视图的中)。
要: 在、或AppDelegate适配器中设置代理。
UNUserNotificationCenter.current().delegate.onAppearApp.initapplication(_:didFinishLaunchingWithOptions:)不要: 在没有AppDelegate适配器的SwiftUI视图中调用。目前没有SwiftUI原生方式接收令牌回调。
要: 所有APNs注册和令牌处理都使用。
UIApplication.shared.registerForRemoteNotifications()UIApplicationDelegateAdaptor不要: 忽略。记录该错误,以便发现设备故障。在模拟器中可静默忽略。
didFailToRegisterForRemoteNotificationsWithError不要: 仅将设备令牌发送至服务器一次。令牌会定期变化。
要: 每次调用时重新发送令牌。
didRegisterForRemoteNotificationsWithDeviceTokenReview Checklist
审核清单
- Authorization requested before registering for remote notifications
- Device token converted to hex string (not )
String(data:encoding:) - set in
UNUserNotificationCenterDelegateorApp.initapplication(_:didFinishLaunching:) - Foreground notification handling implemented ()
willPresent - Notification tap handling implemented with deep linking ()
didReceive - Categories and actions registered at launch if interactive notifications needed
- Badge count managed (reset on app open if appropriate)
- Silent push background handling configured (Background Modes capability enabled)
- used for APNs token callbacks in SwiftUI apps
UIApplicationDelegateAdaptor - Sensitive data not included directly in payload (uses service extension)
- Notification grouping configured with where applicable
threadIdentifier - Denied permission case handled gracefully (Settings link)
- 注册远程通知前已请求授权
- 设备令牌已转换为十六进制字符串(而非)
String(data:encoding:) - 已在
UNUserNotificationCenterDelegate或App.init中设置application(_:didFinishLaunching:) - 已实现前台通知处理()
willPresent - 已实现通知点击处理并支持深层链接()
didReceive - 若需交互式通知,已在启动时注册类别与操作
- 已管理角标计数(如适用,应用打开时重置)
- 已配置静默推送后台处理(已启用后台模式权限)
- SwiftUI应用中使用处理APNs令牌回调
UIApplicationDelegateAdaptor - 敏感数据未直接包含在负载中(使用服务扩展)
- 已使用配置通知分组(如适用)
threadIdentifier - 已优雅处理权限被拒的情况(提供设置链接)
References
参考资料
- — AppDelegate setup, delegate implementation, deep-link router, silent push, scheduling, token refresh, debugging.
references/notification-patterns.md - — Service Extension (media, decryption), Content Extension (custom UI), attachments, communication notifications.
references/rich-notifications.md
- —— AppDelegate设置、代理实现、深层链接路由器、静默推送、调度、令牌刷新、调试。
references/notification-patterns.md - —— 服务扩展(媒体、解密)、内容扩展(自定义UI)、附件、通信通知。
references/rich-notifications.md