You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 11:54:56