Swift开发:Apple Watch本地通知随机内容失效问题
问题描述
Apple Watch应用中,本地通知功能、通知小时间隔设置以及保存按钮均正常工作,但随机消息展示功能异常:调用randomText()函数选中一条内容后,每次到达设置的间隔时间都会重复推送该内容。
原代码文件
主视图代码
import SwiftUI import UserNotifications struct Nottie2: View { @AppStorage("notificationInterval") var notificationInterval: Int = 1 @AppStorage("isSnoozed") var isSnoozed: Bool = false @AppStorage("isNotificationsEnabled") var isNotificationsEnabled: Bool = false @State private var borderColor = Color.orange @State private var buttonText = "Save" var body: some View { VStack { Toggle(isOn: $isNotificationsEnabled) { Text(isNotificationsEnabled ? "Turn off" : "Turn on") } .padding() .onChange(of: isNotificationsEnabled) { enabled in if enabled { requestPermission() } else { disableNotification() } } if isNotificationsEnabled { Picker("Notification Interval", selection: $notificationInterval) { ForEach(1...6, id: \.self) { interval in Text("\(interval) hour\(interval > 1 ? "s" : "")") } }.frame(height: 60) .padding() Button(action: { enableNotification() buttonText = "Saving" borderColor = .green DispatchQueue.main.asyncAfter(deadline: .now() + 2) { buttonText = "Saved" borderColor = .green } DispatchQueue.main.asyncAfter(deadline: .now() + 4) { buttonText = "Save" borderColor = .orange } }) { Text(buttonText) } .foregroundColor(.white) .padding(1) .frame(width: 75) .padding(7) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(borderColor, lineWidth: 2)) .buttonStyle(.plain) } } .onAppear() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if granted { print("Permission granted") } else { print("Permission denied") isNotificationsEnabled = false } } } .onReceive(NotificationCenter.default.publisher(for: WKExtension.applicationDidBecomeActiveNotification)) { _ in if isSnoozed { enableNotification(snooze: true) } } }
请求通知权限函数
private func requestPermission() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if granted { print("Permission granted") enableNotification() } else { print("Permission denied") isNotificationsEnabled = false } } }
通知调度函数
private func enableNotification(snooze: Bool = false) { let content = UNMutableNotificationContent() content.body = randomText() content.sound = UNNotificationSound.default var trigger: UNNotificationTrigger if snooze { trigger = UNTimeIntervalNotificationTrigger(timeInterval: 540, repeats: false) } else { trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(notificationInterval * 3600), repeats: true) } let request = UNNotificationRequest(identifier: "notification", content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) }
随机文本生成函数
func randomText() -> String { let words = ["Place", "Cat", "House"] return words[Int(arc4random_uniform(UInt32(words.count)))] }
其他通知操作函数
private func disableNotification() { UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["notification"]) } private func snoozeNotification() { isSnoozed = true UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["notification"]) enableNotification(snooze: true) } private func dismissNotification() { isSnoozed = false UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["notification"]) } private func showNotificationActions() { let snoozeAction = UNNotificationAction(identifier: "snooze", title: "Snooze", options: []) let dismissAction = UNNotificationAction(identifier: "dismiss", title: "Dismiss", options: [.destructive]) let category = UNNotificationCategory(identifier: "notificationActions", actions: [snoozeAction, dismissAction], intentIdentifiers: [], options: []) UNUserNotificationCenter.current().setNotificationCategories([category]) } } struct Nottie2_Previews: PreviewProvider { static var previews: some View { Nottie2() } }
问题根源
使用重复触发的UNTimeIntervalNotificationTrigger时,通知请求的content是在创建请求时一次性生成的,后续重复触发不会重新调用randomText(),因此每次推送的都是同一条随机内容。另外,当前代码未正确处理通知展示后的重新调度,也没有在通知动作(snooze/dismiss)后正确维护通知队列。
解决方案
- 将重复触发改为单次触发,每次通知推送完成后重新生成随机内容并调度下一次通知
- 实现通知接收代理,监听通知的展示事件,在通知被展示后自动调度下一次通知
- 修正动作处理逻辑,确保snooze/dismiss操作后正确清理旧请求并调度新请求
- 提前注册通知类别,在应用启动时注册通知动作类别,确保通知能显示自定义按钮
修改后的完整代码
import SwiftUI import UserNotifications // 实现通知中心代理 class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate { var parentView: Nottie2? // 通知即将展示时调用 func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert, .sound]) // 调度下一次通知(非snooze场景) if !parentView?.isSnoozed ?? false { parentView?.scheduleNextNotification() } } // 用户点击通知动作时调用 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { switch response.actionIdentifier { case UNNotificationDefaultActionIdentifier: // 用户点击通知本身,调度下一次通知 parentView?.scheduleNextNotification() case "snooze": parentView?.snoozeNotification() case "dismiss": parentView?.dismissNotification() default: break } completionHandler() } } struct Nottie2: View { @AppStorage("notificationInterval") var notificationInterval: Int = 1 @AppStorage("isSnoozed") var isSnoozed: Bool = false @AppStorage("isNotificationsEnabled") var isNotificationsEnabled: Bool = false @State private var borderColor = Color.orange @State private var buttonText = "Save" private let notificationDelegate = NotificationDelegate() var body: some View { VStack { Toggle(isOn: $isNotificationsEnabled) { Text(isNotificationsEnabled ? "关闭通知" : "开启通知") } .padding() .onChange(of: isNotificationsEnabled) { enabled in if enabled { requestPermission() } else { disableNotification() } } if isNotificationsEnabled { Picker("通知间隔", selection: $notificationInterval) { ForEach(1...6, id: \.self) { interval in Text("\(interval)小时\(interval > 1 ? "s" : "")") } }.frame(height: 60) .padding() Button(action: { disableNotification() // 先清除旧请求 scheduleNextNotification() buttonText = "保存中" borderColor = .green DispatchQueue.main.asyncAfter(deadline: .now() + 2) { buttonText = "已保存" borderColor = .green } DispatchQueue.main.asyncAfter(deadline: .now() + 4) { buttonText = "保存" borderColor = .orange } }) { Text(buttonText) } .foregroundColor(.white) .padding(1) .frame(width: 75) .padding(7) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(borderColor, lineWidth: 2)) .buttonStyle(.plain) } } .onAppear() { // 设置通知代理 UNUserNotificationCenter.current().delegate = notificationDelegate notificationDelegate.parentView = self // 注册通知类别 showNotificationActions() UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if granted { print("权限已授予") } else { print("权限被拒绝") isNotificationsEnabled = false } } } .onReceive(NotificationCenter.default.publisher(for: WKExtension.applicationDidBecomeActiveNotification)) { _ in if isSnoozed { scheduleNextNotification(snooze: true) } } } private func requestPermission() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if granted { print("权限已授予") scheduleNextNotification() } else { print("权限被拒绝") isNotificationsEnabled = false } } } // 调度下一次通知 private func scheduleNextNotification(snooze: Bool = false) { let content = UNMutableNotificationContent() content.body = randomText() content.sound = UNNotificationSound.default content.categoryIdentifier = "notificationActions" // 指定类别 let timeInterval: TimeInterval if snooze { timeInterval = 540 // 9分钟 } else { timeInterval = TimeInterval(notificationInterval * 3600) } // 使用单次触发 let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) // 使用唯一标识符,避免覆盖 let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) isSnoozed = snooze } func randomText() -> String { let words = ["Place", "Cat", "House"] return words[Int(arc4random_uniform(UInt32(words.count)))] } private func disableNotification() { UNUserNotificationCenter.current().removeAllPendingNotificationRequests() } private func snoozeNotification() { disableNotification() scheduleNextNotification(snooze: true) } private func dismissNotification() { disableNotification() isSnoozed = false if isNotificationsEnabled { scheduleNextNotification() } } private func showNotificationActions() { let snoozeAction = UNNotificationAction(identifier: "snooze", title: "稍后提醒", options: []) let dismissAction = UNNotificationAction(identifier: "dismiss", title: "取消", options: [.destructive]) let category = UNNotificationCategory(identifier: "notificationActions", actions: [snoozeAction, dismissAction], intentIdentifiers: [], options: []) UNUserNotificationCenter.current().setNotificationCategories([category]) } } struct Nottie2_Previews: PreviewProvider { static var previews: some View { Nottie2() } }
关键修改点
- 新增
NotificationDelegate类实现通知代理,监听通知展示和动作点击事件 - 将重复触发改为单次触发,每次通知展示后自动调度下一次通知
- 使用
UUID().uuidString作为通知请求标识符,避免覆盖旧请求 - 在保存按钮点击时先清除所有旧请求,再调度新通知
- 修正dismiss逻辑,取消后自动调度下一次通知(如果通知仍处于开启状态)
- 提前在
onAppear中注册通知类别和设置代理
内容的提问来源于stack exchange,提问作者Moi
相关产品推荐
相关产品推荐

