You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

iOS Objective-C实现每日动态时间自动触发本地通知问题

解决方案:实现跨日自动触发本地通知

嘿,我完全懂你现在的困扰——你的本地通知只能在当天正常触发,第二天必须打开APP才能重新生效对吧?这本质上是因为你只在APP活跃时设置了当日的通知,没有提前为后续日期预约通知,也没处理后台场景下的日期更新逻辑。下面给你几个iOS平台上实用的落地方案:

1. 首次启动/每月初批量预约当月所有通知

最直接的思路是:当APP首次打开,或者每个月的第一天,一次性遍历你的hoursArrayminutesArray,为当月每一天创建对应的本地通知请求。这样整个月的通知都提前安排给系统,哪怕APP全程关闭,到点系统也会自动触发。

Swift代码示例

import UserNotifications

func scheduleAllMonthlyNotifications() {
    let notificationCenter = UNUserNotificationCenter.current()
    // 先清除已有的同类型通知,避免重复触发
    let notificationIDs = Array(0..<31).map { "daily_reminder_\($0)" }
    notificationCenter.removePendingNotificationRequests(withIdentifiers: notificationIDs)
    
    // 获取当前年月
    guard let currentDateComponents = Calendar.current.dateComponents([.year, .month], from: Date()) else { return }
    let currentYear = currentDateComponents.year!
    let currentMonth = currentDateComponents.month!
    
    // 遍历当月每一天的时分数据
    for dayIndex in 0..<hoursArray.count {
        let targetDay = dayIndex + 1 // 数组索引从0开始,日期从1开始
        guard let targetDate = Calendar.current.date(from: DateComponents(
            year: currentYear,
            month: currentMonth,
            day: targetDay,
            hour: hoursArray[dayIndex],
            minute: minutesArray[dayIndex]
        )) else { continue }
        
        // 跳过已经过去的日期,避免设置无效通知
        if targetDate < Date() { continue }
        
        // 构建通知内容
        let content = UNMutableNotificationContent()
        content.title = "每日提醒"
        content.body = "到了你预设的时间啦!"
        content.sound = .default
        
        // 创建日历触发条件
        let triggerComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: targetDate)
        let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: false)
        
        // 创建并添加通知请求
        let request = UNNotificationRequest(
            identifier: "daily_reminder_\(dayIndex)",
            content: content,
            trigger: trigger
        )
        notificationCenter.add(request) { error in
            if let error = error {
                print("预约通知失败:\(error.localizedDescription)")
            }
        }
    }
}

你可以在APP启动时调用这个方法,或者结合日期判断,每月1号自动重新预约当月所有通知。

2. 用后台刷新任务补全通知

如果用户长期不打开APP(比如跨月了),之前预约的通知可能失效,这时候可以借助iOS的后台刷新任务定期唤醒APP,检查并补充通知。

实现步骤

  1. Info.plist中开启UIBackgroundModesfetch权限;
  2. 注册并处理后台刷新任务:
import BackgroundTasks

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 注册后台刷新任务
    BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.yourapp.refreshNotifications", using: nil) { task in
        self.handleNotificationRefresh(task: task as! BGAppRefreshTask)
    }
    // 首次预约后台刷新
    scheduleNextBackgroundRefresh()
    return true
}

func handleNotificationRefresh(task: BGAppRefreshTask) {
    // 设置任务过期回调
    task.expirationHandler = {
        task.setTaskCompleted(success: false)
    }
    
    // 重新预约当月通知
    scheduleAllMonthlyNotifications()
    
    // 标记任务完成
    task.setTaskCompleted(success: true)
    // 预约下一次后台刷新(比如24小时后)
    scheduleNextBackgroundRefresh()
}

func scheduleNextBackgroundRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: "com.yourapp.refreshNotifications")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 24 * 60 * 60)
    
    do {
        try BGTaskScheduler.shared.submit(request)
    } catch {
        print("提交后台刷新任务失败:\(error.localizedDescription)")
    }
}

后台刷新的频率由系统管控,不能保证精确到小时,但足够应对跨月或通知丢失的场景。

3. 监听系统日期变更通知

当设备跨零点、时区变更时,系统会发送UIApplication.significantTimeChangeNotification通知,你可以监听这个通知,在日期切换时自动更新通知。

代码示例

override func viewDidLoad() {
    super.viewDidLoad()
    // 监听日期变更通知
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(onDateChanged),
        name: UIApplication.significantTimeChangeNotification,
        object: nil
    )
}

@objc func onDateChanged() {
    // 日期变化时,重新检查并预约通知
    scheduleAllMonthlyNotifications()
}

这个方案能确保跨零点后,第二天的通知已经被正确设置,无需用户打开APP。

关键注意事项

  • 必须申请通知权限:在APP启动时调用UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound])获取用户授权,否则通知无法触发;
  • 唯一标识符:每个日期的通知用不同的标识符(比如daily_reminder_0对应1号),避免重复添加,也方便清理旧通知;
  • 数组边界检查:用Calendar.current.range(of: .day, in: .month, for: Date())?.count获取当月实际天数,和你的数组长度对比,避免数组越界崩溃。

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

火山引擎 最新活动