Swift应用本地通知无法触发问题排查及代码求助
本地通知功能故障排查与修复
核心缺失逻辑与问题点
scheduleNotifications()方法完全未实现:ContentView中该方法是空的,没有根据用户选择的店铺、日期、提前时间调用通知处理类的调度方法,这是通知根本不触发的直接原因。- 通知时间计算逻辑错误:原
scheduleNotification方法中直接设置dateComponents.minute = -(minutesBefore)不符合日期组件规范,会导致时间参数无效,正确逻辑应基于店铺关门时间减去提前分钟数计算通知触发时间。 - 未清理旧通知:重新调度时未移除已存在的通知请求,会导致重复通知或旧设置残留。
- 未指定时区:未显式设置本地时区,可能因系统时区差异导致通知时间偏差。
- 权限处理不严谨:仅调用权限申请,未在确认授权后再执行调度,也未处理用户后续关闭权限的场景。
修复后的完整代码
1. 完善ContentView的scheduleNotifications()方法
func scheduleNotifications() { // 先清空所有待触发的旧通知,避免重复 UNUserNotificationCenter.current().removeAllPendingNotificationRequests() // 定义各店铺实际关门时间(请根据业务需求修改) let olsalgetClosing = (hour: 20, minute: 0) let vinmonopoletClosing = (hour: 18, minute: 0) // 遍历用户选中的日期 for (index, isSelected) in daysSwitch.enumerated() { guard isSelected else { continue } let day = days[index] // 处理Ølsalget的通知调度 if olsalgetSwitch { let notifyTime = calculateNotifyTime(closingHour: olsalgetClosing.hour, closingMinute: olsalgetClosing.minute, advanceMinutes: timeInterval) notify.scheduleNotification(for: day, notifyHour: notifyTime.hour, notifyMinute: notifyTime.minute, title: "Ølsalget snart stengt!", body: "Du har \(timeInterval) minutter igjen til stenging.") } // 处理Vinmonopolet的通知调度 if vinmonopoletSwitch { let notifyTime = calculateNotifyTime(closingHour: vinmonopoletClosing.hour, closingMinute: vinmonopoletClosing.minute, advanceMinutes: timeInterval) notify.scheduleNotification(for: day, notifyHour: notifyTime.hour, notifyMinute: notifyTime.minute, title: "Vinmonopolet snart stengt!", body: "Du har \(timeInterval) minutter igjen til stenging.") } } } // 辅助计算通知触发时间的方法 private func calculateNotifyTime(closingHour: Int, closingMinute: Int, advanceMinutes: Int) -> (hour: Int, minute: Int) { var totalMinutes = closingHour * 60 + closingMinute - advanceMinutes // 处理跨天情况(比如凌晨关门提前通知的场景) if totalMinutes < 0 { totalMinutes += 24 * 60 } return (totalMinutes / 60, totalMinutes % 60) }
2. 修改NotificationHandler的scheduleNotification方法
func scheduleNotification(for day: String, notifyHour: Int, notifyMinute: Int, title: String, body: String) { var dateComponents = DateComponents() dateComponents.timeZone = .current // 显式指定本地时区 dateComponents.hour = notifyHour dateComponents.minute = notifyMinute // 映射挪威语星期到Calendar组件的weekday值(1为周日,2为周一,以此类推) switch day { case "Mandag": dateComponents.weekday = 2 case "Tirsdag": dateComponents.weekday = 3 case "Onsdag": dateComponents.weekday = 4 case "Torsdag": dateComponents.weekday = 5 case "Fredag": dateComponents.weekday = 6 case "Lørdag": dateComponents.weekday = 7 default: return } // 创建重复触发的日历触发器 let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) let content = UNMutableNotificationContent() content.title = title content.body = body content.sound = UNNotificationSound.default content.badge = 1 // 使用固定格式的标识符,方便后续更新或移除指定通知 let identifier = "\(title.replacingOccurrences(of: " ", with: "_"))_\(day)" let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) { error in if let error = error { print("调度通知出错: \(error)") } } }
3. 优化权限校验逻辑
在ContentView的onAppear中,先确认权限状态再执行调度:
.onAppear { UNUserNotificationCenter.current().getNotificationSettings { settings in if settings.authorizationStatus != .authorized { self.notify.askPermission() } // 回到主线程执行UI相关的调度逻辑 DispatchQueue.main.async { self.scheduleNotifications() } } }
额外注意事项
- 必须在
Info.plist中添加NSUserNotificationUsageDescription字段,填写通知用途说明,否则系统会拒绝权限申请。 - 测试时建议使用真机,模拟器的通知展示逻辑与真机存在差异。
- 可在设置变更时增加权限状态检查,若用户已关闭权限,可弹窗引导用户前往系统设置开启。
内容的提问来源于stack exchange,提问作者Johannes With
相关产品推荐
相关产品推荐

