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
相关产品推荐
相关产品推荐

