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

Swift中如何在两个日期之间设置UNLocalNotification?

你说得对,UNNotificationRequest本身确实没有直接指定起止日期的属性,不过我们可以通过两种常见的方案来实现「在两个日期之间调度用药提醒」的需求,下面给你详细拆解和代码示例:

方案一:预先生成所有触发日期,创建独立通知请求

这种方式适合起止日期范围不大的场景,我们先算出两个日期之间所有需要触发的时间点,为每个时间点单独创建通知请求,精准控制触发范围。

假设你的需求是:从startDate到endDate,每天的hour:minutes时刻触发吃药提醒,代码可以这样写:

import UserNotifications

static func setMedicationAlarms(from startDate: Date, to endDate: Date, hour: Int, minutes: Int, medicineName: String, medicineId: String) {
    let center = UNUserNotificationCenter.current()
    
    // 先取消该药品已有的通知,避免重复创建
    center.removePendingNotificationRequests(withIdentifiers: [medicineId + "*"])
    
    // 配置触发的时分组件
    let calendar = Calendar.current
    var targetComponents = DateComponents()
    targetComponents.hour = hour
    targetComponents.minute = minutes
    
    // 遍历起止日期之间的每一天,生成对应触发时间
    var currentDate = startDate
    while currentDate <= endDate {
        // 合并当前日期与目标时分,生成精准触发时间
        guard let triggerDate = calendar.date(bySettingHour: hour, minute: minutes, second: 0, of: currentDate) else {
            currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
            continue
        }
        
        // 构建通知内容
        let content = UNMutableNotificationContent()
        content.title = NSLocalizedString("MY_MEDICINE", comment: "药品提醒")
        content.body = "该吃\(medicineName)啦!"
        content.sound = UNNotificationSound.default
        content.userInfo = ["medicineId": medicineId]
        
        // 创建日历触发器(不重复,因为我们已逐个生成所有触发点)
        let triggerComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: triggerDate)
        let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: false)
        
        // 生成唯一通知ID(药品ID+时间戳,确保不重复)
        let requestId = "\(medicineId)_\(triggerDate.timeIntervalSince1970)"
        
        // 添加通知请求
        let request = UNNotificationRequest(identifier: requestId, content: content, trigger: trigger)
        center.add(request) { error in
            if let error = error {
                print("添加\(medicineName)提醒失败:\(error.localizedDescription)")
            }
        }
        
        // 推进到下一天
        currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
    }
}
方案二:设置重复触发器,触发时校验结束日期

如果起止日期跨度很大,生成大量通知请求会占用不必要的系统资源,这时可以设置重复触发器,每次通知触发时检查是否已超过结束日期,若过期则取消后续重复提醒。

第一步:创建重复通知请求

static func setRepeatingMedicationAlarm(from startDate: Date, to endDate: Date, hour: Int, minutes: Int, medicineName: String, medicineId: String) {
    let center = UNUserNotificationCenter.current()
    
    // 取消该药品已有的重复通知
    center.removePendingNotificationRequests(withIdentifiers: [medicineId])
    
    // 配置触发的时分组件
    var targetComponents = DateComponents()
    targetComponents.hour = hour
    targetComponents.minute = minutes
    
    // 创建每日重复触发器(可根据需求修改repeatInterval,比如.weekOfYear实现每周提醒)
    let trigger = UNCalendarNotificationTrigger(dateMatching: targetComponents, repeats: true)
    
    // 构建通知内容,将结束日期存入userInfo供后续校验
    let content = UNMutableNotificationContent()
    content.title = NSLocalizedString("MY_MEDICINE", comment: "药品提醒")
    content.body = "该吃\(medicineName)啦!"
    content.sound = UNNotificationSound.default
    content.userInfo = [
        "medicineId": medicineId,
        "endDate": endDate.timeIntervalSince1970 // 用时间戳存储,避免日期序列化问题
    ]
    
    // 添加重复通知请求
    let request = UNNotificationRequest(identifier: medicineId, content: content, trigger: trigger)
    center.add(request) { error in
        if let error = error {
            print("添加\(medicineName)重复提醒失败:\(error.localizedDescription)")
        }
    }
}

第二步:实现通知代理,校验结束日期

你需要在AppDelegate或专门的通知管理类中设置UNUserNotificationCenter的代理,实现触发校验逻辑:

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 请求通知权限
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
            if granted {
                print("通知权限已获取")
            }
        }
        return true
    }
    
    // 前台收到通知时触发
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        checkIfAlarmExpired(notification: notification)
        completionHandler([.alert, .sound])
    }
    
    // 用户点击通知时触发
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        checkIfAlarmExpired(notification: response.notification)
        completionHandler()
    }
    
    // 校验当前通知是否已过期
    private func checkIfAlarmExpired(notification: UNNotification) {
        guard let endDateTimestamp = notification.request.content.userInfo["endDate"] as? TimeInterval,
              let medicineId = notification.request.content.userInfo["medicineId"] as? String else {
            return
        }
        
        let endDate = Date(timeIntervalSince1970: endDateTimestamp)
        let currentDate = Date()
        
        if currentDate > endDate {
            // 已超过结束日期,取消后续重复通知
            UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [medicineId])
            print("药品\(medicineId)的提醒已过期,已自动取消后续通知")
        }
    }
}

额外注意事项

  • 别忘了在Info.plist中添加NSUserNotificationUsageDescription(iOS 10+),说明应用需要通知权限的原因,否则会被系统拒绝授权。
  • 如果你的提醒是间隔特定天数(比如每3天一次),方案一更灵活——只需修改currentDate的推进步长即可;方案二的repeatInterval仅支持系统预设的日历单位(如.day、.weekOfYear)。
  • 两种方案都建议先取消已有通知,避免重复创建导致的逻辑混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:26:55