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

Swift中按指定日期时段调度本地通知及突破64条限制方案

解决方案

问题根源

  1. 触发器类型错误:原代码使用UNTimeIntervalNotificationTrigger仅能触发单次通知,无法实现每周重复的指定星期几逻辑
  2. 星期几逻辑未整合到触发器:未将daysOfWeek参数绑定到日历触发器的重复规则中
  3. 未处理iOS通知数量限制:iOS系统限制待调度通知总数不超过64条,直接调度140条会被系统丢弃

改进后的实现代码

import UserNotifications

class NotificationScheduler {
    private let center = UNUserNotificationCenter.current()
    private let calendar = Calendar.current
    
    // 初始化时请求通知权限
    func requestNotificationPermissions() {
        center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            if let error = error {
                print("权限请求失败: \(error.localizedDescription)")
            }
        }
    }
    
    /// 调度指定星期几、时间段内的重复通知
    /// - Parameters:
    ///   - numberOfNotificationsPerDay: 每个指定星期几发送的通知数量
    ///   - targetWeekdays: 目标星期几(1=周日, 2=周一, ...,7=周六)
    ///   - startTime: 时间段开始时间(仅需hour和minute)
    ///   - endTime: 时间段结束时间(仅需hour和minute)
    func scheduleRepeatingNotifications(numberOfNotificationsPerDay: Int,
                                         targetWeekdays: [Int],
                                         startTime: DateComponents,
                                         endTime: DateComponents) {
        // 验证时间范围有效性
        guard let startHour = startTime.hour, let startMinute = startTime.minute,
              let endHour = endTime.hour, let endMinute = endTime.minute else {
            print("时间参数无效")
            return
        }
        
        let totalMinutes = (endHour - startHour) * 60 + (endMinute - startMinute)
        guard totalMinutes > 0 else {
            print("结束时间必须晚于开始时间")
            return
        }
        
        // 计算每条通知的间隔(确保至少1分钟)
        let interval = max(totalMinutes / numberOfNotificationsPerDay, 1)
        
        // 生成每个通知的时间偏移量(分钟)
        let timeOffsets = (0..<numberOfNotificationsPerDay).map { $0 * interval }
        
        // 先取消已存在的同类型通知,避免重复调度
        center.removePendingNotificationRequests(withIdentifiers: getNotificationIdentifiers(targetWeekdays: targetWeekdays, offsets: timeOffsets))
        
        // 计算当前可调度的最大数量,避免超过iOS限制
        let maxBatchSize = 64
        var pendingRequests = [UNNotificationRequest]()
        
        for weekday in targetWeekdays {
            for offset in timeOffsets {
                // 计算当前通知的具体时间点
                let notificationHour = startHour + (offset / 60)
                let notificationMinute = startMinute + (offset % 60)
                
                // 构建日历触发器的日期组件
                var triggerComponents = DateComponents()
                triggerComponents.weekday = weekday
                triggerComponents.hour = notificationHour
                triggerComponents.minute = notificationMinute
                
                // 创建通知内容
                let content = UNMutableNotificationContent()
                content.title = "定时通知"
                content.body = "这是第\(offset/interval + 1)条通知"
                content.sound = .default
                
                // 生成唯一标识符,用于后续管理
                let identifier = "weekday_\(weekday)_offset_\(offset)"
                
                // 创建重复的日历触发器
                let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: true)
                
                let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
                pendingRequests.append(request)
                
                // 达到批量上限时立即调度,并清空队列
                if pendingRequests.count >= maxBatchSize {
                    center.add(pendingRequests)
                    pendingRequests.removeAll()
                }
            }
        }
        
        // 调度剩余的请求
        if !pendingRequests.isEmpty {
            center.add(pendingRequests)
        }
        
        // 设置通知触发回调,用于补充调度未来周期(如果需要超过64条的长期调度)
        center.delegate = self
    }
    
    /// 生成通知标识符列表
    private func getNotificationIdentifiers(targetWeekdays: [Int], offsets: [Int]) -> [String] {
        var identifiers = [String]()
        for weekday in targetWeekdays {
            for offset in offsets {
                identifiers.append("weekday_\(weekday)_offset_\(offset)")
            }
        }
        return identifiers
    }
}

// 实现UNUserNotificationCenterDelegate,处理通知触发后的补充调度
extension NotificationScheduler: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // 如果需要实现超过64条的长期调度(比如每周自动补充),可以在这里处理
        // 例如:当一条通知触发后,调度下一个周期的对应通知(如果当前待调度数量不足)
        // 这里可以根据业务需求扩展逻辑
        
        completionHandler()
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // 前台显示通知的逻辑
        completionHandler([.alert, .sound])
    }
}

// 使用示例
let scheduler = NotificationScheduler()
scheduler.requestNotificationPermissions()

// 示例:周三(4)、周六(7)的14:00-17:00之间发送20条通知
let startTime = DateComponents(hour: 14, minute: 0)
let endTime = DateComponents(hour: 17, minute: 0)
scheduler.scheduleRepeatingNotifications(numberOfNotificationsPerDay: 20,
                                         targetWeekdays: [4,7],
                                         startTime: startTime,
                                         endTime: endTime)

关键改进点说明

  • 使用日历触发器:UNCalendarNotificationTrigger结合weekday参数实现每周重复的指定星期几通知,解决原代码无法重复的问题
  • 批量调度处理64条限制:将通知请求分批,每达到64条就立即调度,避免超过系统限制
  • 通知标识符管理:为每个通知生成唯一标识符,方便后续取消或更新
  • 权限请求:新增权限请求逻辑,确保应用拥有通知权限
  • 回调扩展:实现UNUserNotificationCenterDelegate,可在通知触发后补充调度未来周期的通知,支持超过64条的长期需求

注意事项

  • 星期几的数值对应关系:Calendar.current中weekday的取值为1=周日,2=周一,3=周二,4=周三,5=周四,6=周五,7=周六
  • 如果需要精确控制长期调度(比如超过4周的通知),可以在didReceive回调中,当通知触发后,重新调度下一个周期的对应通知,保证待调度数量始终不超过64条
  • 测试时可通过Xcode的“Simulate Notifications”功能快速验证触发器逻辑

内容的提问来源于stack exchange,提问作者user24192912

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 08:08:17