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

Swift实现指定时间段内按用户选择日期发送重复本地通知

Swift实现指定日期时段内间隔重复本地通知+CollectionView选中日期传递方案

需求与问题

需求

希望在上午10点至晚上10点之间,按照用户输入的时间间隔(示例为2小时),仅在用户选中的日期重复发送本地通知。

问题

  1. 如何在Swift中实现上述通知调度逻辑?
  2. 使用CollectionView展示日期选项时,在didSelectItemAt方法中如何传递用户选中的日期?

用户提供的原始代码

var dateComponents = DateComponents()
dateComponents.hour = 10
dateComponents.minute = 00
let userCalendar = Calendar.current
var dateStart = userCalendar.date(from: dateComponents)
var dateComponent = DateComponents()
dateComponent.hour = 22
dateComponent.minute = 00
var dateEnd = userCalendar.date(from: dateComponent)

while dateStart < dateEnd {
    //Add repeating hours start time
    UserDefaults.standard.set(self.txtField.text, forKey: "hoursSendary")
    let Nohours = UserDefaults.standard.integer(forKey: "hoursSendary")
    dateStart = dateStart?.addingTimeInterval(TimeInterval(Nohours * 60 * 60))
    //Schedule notification With body and title.
    scheduleNotification(body: "Free your eyes", titles: "Screen Alarm",day: 1)
}

func scheduleNotification( body: String, titles:String,day:Int) {
    UserDefaults.standard.set(self.txtField.text, forKey: "hoursSendary")
    let Nohours = UserDefaults.standard.integer(forKey: "hoursSendary")
    let date = Date(timeIntervalSinceNow:TimeInterval(Nohours * 60 * 60) )

    var components = DateComponents()
    components.weekday = day// sunday = 1 ... saturday = 7
    components.weekdayOrdinal = 10
    components.timeZone = .current
    components.hour = Nohours
    let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
    let content = UNMutableNotificationContent()
    content.title = titles
    content.body = body
    content.sound = UNNotificationSound.default
    content.categoryIdentifier = "ScreenAlarm"
    let request = UNNotificationRequest(identifier: "NotificationAt-\(date))", content: content, trigger: trigger)
    UNUserNotificationCenter.current().delegate = self
    //UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
    UNUserNotificationCenter.current().add(request) {(error) in
        if let error = error {
            print("Uh oh! We had an error: \(error)")
        }
    }
}

解决方案

一、实现指定日期时段内间隔重复本地通知

原代码问题分析

  1. dateStart和dateEnd仅设置了时分,未关联用户选中的具体日期,会默认使用当前日期
  2. scheduleNotification中DateComponents配置错误:weekdayOrdinal = 10无意义,且未绑定选中日期的年/月/日
  3. 重复读写UserDefaults属于冗余操作,直接传递参数即可
  4. 通知标识符生成存在语法错误(多了一个右括号)

修正后的实现步骤与代码

  1. 先存储用户选中的日期集合
  2. 为每个选中日期生成当天10:00到22:00之间的所有触发时间点
  3. 基于每个时间点创建通知请求并调度
1. 定义存储选中日期的变量
var selectedDates: [Date] = [] // 存储用户从CollectionView选中的日期
2. 生成所有通知触发条件
func generateNotificationTriggers(forSelectedDates dates: [Date], intervalHours: Int) -> [UNCalendarNotificationTrigger] {
    let calendar = Calendar.current
    var triggers: [UNCalendarNotificationTrigger] = []
    
    for date in dates {
        // 提取选中日期的年、月、日
        guard let year = calendar.dateComponents([.year], from: date).year,
              let month = calendar.dateComponents([.month], from: date).month,
              let day = calendar.dateComponents([.day], from: date).day else {
            continue
        }
        
        // 当天开始时间:10:00
        var startComponents = DateComponents()
        startComponents.year = year
        startComponents.month = month
        startComponents.day = day
        startComponents.hour = 10
        startComponents.minute = 0
        
        // 当天结束时间:22:00
        var endComponents = DateComponents()
        endComponents.year = year
        endComponents.month = month
        endComponents.day = day
        endComponents.hour = 22
        endComponents.minute = 0
        
        guard var currentTime = calendar.date(from: startComponents),
              let endTime = calendar.date(from: endComponents) else {
            continue
        }
        
        // 遍历生成所有间隔时间点
        while currentTime <= endTime {
            let triggerComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: currentTime)
            let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: false)
            triggers.append(trigger)
            
            // 增加间隔小时
            currentTime = calendar.date(byAdding: .hour, value: intervalHours, to: currentTime)!
        }
    }
    
    return triggers
}
3. 调度通知
func scheduleNotifications(forTriggers triggers: [UNCalendarNotificationTrigger], body: String, title: String) {
    let center = UNUserNotificationCenter.current()
    center.delegate = self
    
    // 可选:移除已有待发送通知,避免重复调度
    center.removeAllPendingNotificationRequests()
    
    for (index, trigger) in triggers.enumerated() {
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = UNNotificationSound.default
        content.categoryIdentifier = "ScreenAlarm"
        
        // 生成唯一标识符
        let request = UNNotificationRequest(identifier: "ScreenAlarm-\(index)", content: content, trigger: trigger)
        center.add(request) { error in
            if let error = error {
                print("通知调度失败:\(error.localizedDescription)")
            }
        }
    }
}
4. 调用示例(用户确认后执行)
// 获取用户输入的间隔小时
guard let intervalText = txtField.text, 
      let intervalHours = Int(intervalText), 
      intervalHours > 0 else {
    // 处理输入无效的情况,比如弹出提示
    print("请输入有效的间隔小时数")
    return
}

// 生成所有触发条件
let triggers = generateNotificationTriggers(forSelectedDates: selectedDates, intervalHours: intervalHours)

// 调度通知
scheduleNotifications(forTriggers: triggers, body: "Free your eyes", title: "Screen Alarm")

二、CollectionView选中日期的传递

实现思路

  1. 准备日期数据源(比如未来7天的日期)
  2. 在didSelectItemAt中获取选中索引对应的日期,维护选中日期集合
  3. 自定义Cell展示日期并反馈选中状态
1. 准备日期数据源
var dateOptions: [Date] = []

// 初始化时生成日期选项(示例:未来7天)
func setupDateOptions() {
    let calendar = Calendar.current
    for dayOffset in 0..<7 {
        guard let date = calendar.date(byAdding: .day, value: dayOffset, to: Date()) else { continue }
        dateOptions.append(date)
    }
}
2. CollectionView代理方法实现
// MARK: - UICollectionView DataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return dateOptions.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DateCell", for: indexPath) as! DateCollectionViewCell
    let date = dateOptions[indexPath.item]
    cell.configure(with: date)
    // 设置初始选中状态
    cell.isSelected = selectedDates.contains(date)
    return cell
}

// MARK: - UICollectionView Delegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let selectedDate = dateOptions[indexPath.item]
    
    // 多选逻辑:切换选中状态
    if let index = selectedDates.firstIndex(of: selectedDate) {
        selectedDates.remove(at: index)
    } else {
        selectedDates.append(selectedDate)
    }
    
    // 更新选中Cell的样式
    if let cell = collectionView.cellForItem(at: indexPath) as? DateCollectionViewCell {
        cell.isSelected.toggle()
    }
    
    // 单选逻辑(替换多选逻辑):
    // selectedDates = [selectedDate]
    // collectionView.reloadData()
}
3. 自定义日期Cell
class DateCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var dateLabel: UILabel!
    
    override var isSelected: Bool {
        didSet {
            // 切换选中样式
            backgroundColor = isSelected ? .systemBlue : .systemGray5
            dateLabel.textColor = isSelected ? .white : .label
        }
    }
    
    func configure(with date: Date) {
        let formatter = DateFormatter()
        formatter.dateFormat = "MM/dd" // 可自定义格式,比如"EEE, MMM dd"
        dateLabel.text = formatter.string(from: date)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 19:54:27