Swift:如何判断本地通知已送达并据此更新UI?
判断本地通知是否送达并更新UI的Swift实现
在Swift里处理本地通知的送达状态确实有点 tricky,因为iOS并没有直接提供「通知已展示在用户屏幕上」的回调,但我们可以通过结合通知中心代理和本地状态追踪来实现你要的效果,包括更新UI显示“Time’s up”。下面是具体的实现思路和代码示例:
核心思路
iOS的UNUserNotificationCenter没有专门的“通知已送达”回调,但我们可以通过两种代理方法追踪通知的到达事件,再配合本地存储的任务状态来更新UI:
- 当App处于前台时,通知到达会触发
willPresent代理方法 - 当App处于后台/被杀死,用户点击通知打开App时,会触发
didReceive代理方法 - 同时,App启动时可以检查未触发的通知列表,标记已触发的任务状态
步骤1:设置通知权限与代理
首先在AppDelegate或SceneDelegate中请求通知权限,并设置通知中心的代理:
import UserNotifications class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 请求通知权限(按需添加权限类型) UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in if let error = error { print("请求通知权限失败:\(error.localizedDescription)") } else if granted { print("通知权限已获取") } } // 设置代理以监听通知事件 UNUserNotificationCenter.current().delegate = self return true } }
步骤2:实现代理方法追踪通知到达
通过代理方法捕获通知到达事件,并更新任务状态:
// 扩展AppDelegate实现通知代理方法 extension AppDelegate { // 前台收到通知时触发(此时通知会直接展示在App内) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { let notificationID = notification.request.identifier // 更新对应任务的状态为「已送达」 updateTaskStatus(for: notificationID, to: .delivered) // 通知UI层更新(回到主线程) DispatchQueue.main.async { NotificationCenter.default.post(name: NSNotification.Name("TaskStatusUpdated"), object: notificationID) } // 配置前台展示通知的方式(比如弹窗+声音) completionHandler([.alert, .sound]) } // 用户点击通知打开App时触发 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let notificationID = response.notification.request.identifier updateTaskStatus(for: notificationID, to: .delivered) DispatchQueue.main.async { NotificationCenter.default.post(name: NSNotification.Name("TaskStatusUpdated"), object: notificationID) } completionHandler() } // 辅助方法:更新本地存储的任务状态 private func updateTaskStatus(for notificationID: String, to status: TaskStatus) { // 这里用UserDefaults做示例,实际项目可以用CoreData/Realm等 var tasks = UserDefaults.standard.dictionary(forKey: "ScheduledTasks") as? [String: [String: Any]] ?? [:] if var task = tasks[notificationID] { task["status"] = status.rawValue tasks[notificationID] = task UserDefaults.standard.set(tasks, forKey: "ScheduledTasks") } } } // 自定义任务状态枚举 enum TaskStatus: String { case pending = "pending" // 待触发 case delivered = "delivered" // 已送达 }
步骤3:在UI层监听状态更新并刷新UI
在你的任务列表页面(比如UITableViewController)中,监听状态变化通知,更新对应的UI元素:
class TaskListViewController: UITableViewController { // 假设你的任务数据源 var scheduledTasks: [Task] = [] override func viewDidLoad() { super.viewDidLoad() // 监听任务状态更新通知 NotificationCenter.default.addObserver(self, selector: #selector(updateTaskUI(_:)), name: NSNotification.Name("TaskStatusUpdated"), object: nil) // App启动时检查未触发的通知,更新已完成的任务状态 checkPendingNotificationsAndUpdateTasks() } @objc private func updateTaskUI(_ notification: NSNotification) { guard let notificationID = notification.object as? String else { return } // 根据通知ID找到对应的任务索引 if let index = scheduledTasks.firstIndex(where: { $0.notificationID == notificationID }) { let indexPath = IndexPath(row: index, section: 0) // 更新对应cell的文本为「Time’s up」 if let cell = tableView.cellForRow(at: indexPath) as? TaskCell { cell.statusLabel.text = "Time’s up" } // 更新数据源状态 scheduledTasks[index].status = .delivered } } // 检查未触发的通知,标记已触发的任务 private func checkPendingNotificationsAndUpdateTasks() { UNUserNotificationCenter.current().getPendingNotificationRequests { [weak self] requests in let pendingNotificationIDs = requests.map { $0.identifier } guard let self = self else { return } // 遍历本地任务,不在pending列表中的即为已触发 for (index, task) in self.scheduledTasks.enumerated() { if !pendingNotificationIDs.contains(task.notificationID) { self.scheduledTasks[index].status = .delivered } } // 回到主线程刷新UI DispatchQueue.main.async { self.tableView.reloadData() } } } // 自定义任务模型 struct Task { let title: String let scheduledTime: Date let notificationID: String var status: TaskStatus } } // 自定义任务Cell示例 class TaskCell: UITableViewCell { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var timeLabel: UILabel! @IBOutlet weak var statusLabel: UILabel! }
注意事项
- 唯一标识:每个本地通知请求必须设置唯一的
identifier(比如UUID),否则会覆盖之前的通知,导致状态追踪错误。 - 后台/杀死状态限制:如果App被完全杀死且用户没有点击通知,我们无法实时更新UI,只能在下次App启动时通过
checkPendingNotificationsAndUpdateTasks方法来补全状态。 - 主线程更新UI:所有UI操作必须在主线程执行,所以要用
DispatchQueue.main.async包裹。
内容的提问来源于stack exchange,提问作者Osama Naeem
相关产品推荐
相关产品推荐

