Swift中实现从起始时间到结束时间的本地定时通知及权限异常问题排查
解决批量创建定时通知时权限请求重复的问题
看起来你遇到的核心问题是每次创建通知都重复请求权限,而且没有正确处理权限请求的异步逻辑,这才导致控制台大量输出"not granted"。我来帮你拆解问题并给出修复方案:
问题根源
- 重复请求权限:你的
createNotification方法每次被调用(也就是循环里的每一次)都会执行center.requestAuthorization。但用户只需要授权一次——当用户已经选择过允许或拒绝后,后续的权限请求会直接返回结果,不会再弹框,这就是你看到大量"not granted"的原因(如果用户一开始拒绝了,或者授权后重复请求被系统直接拦截)。 - 异步逻辑顺序错误:
requestAuthorization是异步执行的,你在发起请求后立刻就创建并添加通知请求,这时候权限可能还没被授予,导致通知无法正常添加。 - 时间计算bug:循环里的
hour = startHour + frequency * num没有处理超过24小时的情况,比如startHour是22,frequency是2,num=1的话得到24,这会导致DateComponents的hour无效。
修复方案
1. 把权限请求抽离,只执行一次
创建一个专门的方法来检查并请求权限,确保整个App生命周期内只做必要的权限请求。
2. 确保权限通过后再创建通知
在权限请求的回调里(或者确认权限已授予后)再执行通知创建逻辑,避免权限未就绪就操作。
3. 修复时间计算逻辑
处理小时超过24的情况,用模运算% 24来保证hour在0-23范围内,同时优化时长计算逻辑,用分钟维度计算更准确。
修复后的完整代码
import Foundation import UserNotifications struct NotificationManager{ let center = UNUserNotificationCenter.current() // 检查权限状态,无权限则请求,有权限直接回调成功 func checkAndRequestNotificationPermission(completion: @escaping (Bool) -> Void) { center.getNotificationSettings { settings in switch settings.authorizationStatus { case .authorized, .provisional: completion(true) case .notDetermined: self.center.requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if let err = error { print("权限请求错误: \(err)") } completion(granted) } default: completion(false) } } } func createNotification(startHour : Int, startMins : Int){ // 创建通知内容 let content = UNMutableNotificationContent() content.title = "Notification!" content.body = "This is a notification" // 处理小时超过24的情况,保证hour在0-23区间 let adjustedHour = startHour % 24 // 构建触发时间组件 var startDate = DateComponents() startDate.calendar = Calendar.current startDate.hour = adjustedHour startDate.minute = startMins print("计划通知时间: \(adjustedHour):\(startMins)") let trigger = UNCalendarNotificationTrigger(dateMatching: startDate, repeats: true) // 创建并添加通知请求 let uuidString = UUID().uuidString let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger) center.add(request) { error in if let err = error { print("添加通知失败: \(err)") } else { print("通知已成功计划") } } } func removeAllNotifications(){ center.removeAllPendingNotificationRequests() } func scheduleNotifications(startHour: Int, startMinute: Int, endHour: Int, endMinute: Int, frequency: Int){ removeAllNotifications() // 用分钟计算总活跃时长,避免跨天计算错误 let startTotalMins = startHour * 60 + startMinute let endTotalMins = endHour * 60 + endMinute let activeTotalMins: Int if endTotalMins < startTotalMins { activeTotalMins = (24 * 60 - startTotalMins) + endTotalMins } else { activeTotalMins = endTotalMins - startTotalMins } let activeHours = activeTotalMins / 60 print("活跃时长: \(activeHours)小时") // 计算通知数量,确保至少1个,且不超过结束时间 let intervalMins = frequency * 60 var numberOfNotifications = activeTotalMins / intervalMins if numberOfNotifications == 0 { numberOfNotifications = 1 } print("计划通知数量: \(numberOfNotifications)个") // 先确认权限,再批量创建通知 checkAndRequestNotificationPermission { granted in guard granted else { print("没有通知权限,无法创建通知") return } // 循环创建通知,从0开始计数避免第一个通知直接跳过起始时间 for num in 0..<numberOfNotifications { let addedHours = frequency * num let targetHour = startHour + addedHours self.createNotification(startHour: targetHour, startMins: startMinute) } } } }
使用示例
现在你可以通过调用scheduleNotifications来批量创建通知,方法语义更清晰,且会自动处理权限和时间问题:
let notificationManager = NotificationManager() // 示例:从9点到17点,每2小时发送一次重复通知 notificationManager.scheduleNotifications(startHour: 9, startMinute: 0, endHour: 17, endMinute: 0, frequency: 2)
内容的提问来源于stack exchange,提问作者Fernando Ivan Perez Ruiz
相关产品推荐
相关产品推荐

