Swift中如何实现用户自定义时间段禁用推送通知
嘿,刚好我之前做过类似的功能,Swift完全支持实现自定义时间段禁用推送通知的需求,下面给你详细拆解实现思路和步骤:
一、核心思路说明
iOS系统本身并没有直接提供「按时间段自动禁用推送」的原生API,但我们可以通过客户端本地拦截的方式实现这个功能——简单来说,就是在收到推送通知时,判断当前时间是否处于用户设置的禁用时间段,再决定是否展示通知。
如果需要完全禁止后台推送的展示,还可以配合后端逻辑:在用户设置的禁用时间段内,后端暂停向APNs发送推送请求。不过客户端侧的本地拦截是最基础且必须的部分。
二、具体实现步骤
1. 存储用户自定义的禁用时间段
我们可以用UserDefaults来持久化用户设置的时间段,比如存储开始/结束的小时和分钟:
// 保存用户设置的免打扰时间段 func saveDoNotDisturbPeriod(startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) { UserDefaults.standard.set(startHour, forKey: "DNDStartHour") UserDefaults.standard.set(startMinute, forKey: "DNDStartMinute") UserDefaults.standard.set(endHour, forKey: "DNDEndHour") UserDefaults.standard.set(endMinute, forKey: "DNDEndMinute") // 保存后可以刷新通知相关状态 } // 获取已保存的免打扰时间段 func getDoNotDisturbPeriod() -> (startHour: Int?, startMinute: Int?, endHour: Int?, endMinute: Int?) { let defaults = UserDefaults.standard let startHour = defaults.object(forKey: "DNDStartHour") as? Int let startMinute = defaults.object(forKey: "DNDStartMinute") as? Int let endHour = defaults.object(forKey: "DNDEndHour") as? Int let endMinute = defaults.object(forKey: "DNDEndMinute") as? Int return (startHour, startMinute, endHour, endMinute) }
2. 判断当前时间是否在禁用时间段
写一个工具函数来检查当前时间是否处于用户设置的免打扰区间,注意要处理跨天的情况(比如22:00到次日06:00):
func isInDoNotDisturbPeriod() -> Bool { let (startHour, startMinute, endHour, endMinute) = getDoNotDisturbPeriod() guard let startH = startHour, let startM = startMinute, let endH = endHour, let endM = endMinute else { return false // 未设置时默认不禁用 } let currentDate = Date() let calendar = Calendar.current guard let currentH = calendar.dateComponents([.hour], from: currentDate).hour, let currentM = calendar.dateComponents([.minute], from: currentDate).minute else { return false } // 转换为总分钟数,方便比较 let startTotal = startH * 60 + startM let endTotal = endH * 60 + endM let currentTotal = currentH * 60 + currentM if startTotal < endTotal { // 时间段在同一天(如08:00-18:00) return currentTotal >= startTotal && currentTotal <= endTotal } else { // 时间段跨天(如22:00-06:00) return currentTotal >= startTotal || currentTotal <= endTotal } }
3. 拦截推送通知的展示
通过UNUserNotificationCenterDelegate的方法,在收到推送时判断是否展示:
// 可以在AppDelegate或者专门的通知管理类中实现 extension AppDelegate: UNUserNotificationCenterDelegate { // 前台收到推送时的回调 func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { if isInDoNotDisturbPeriod() { // 处于禁用时间段,不展示通知 completionHandler([]) } else { // 正常展示通知,可根据需求设置选项(横幅、声音等) completionHandler([.banner, .sound]) } } // 通知被点击后的回调(不管是否在禁用时间段,用户点击都可以正常处理) func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // 处理通知点击逻辑,比如跳转对应页面 completionHandler() } }
4. 设置页面的UI实现
用时间选择器让用户设置免打扰时间段,这里以UIKit为例(SwiftUI可以用DatePicker绑定@AppStorage变量,更简洁):
class NotificationSettingsVC: UIViewController { private let startDatePicker = UIDatePicker() private let endDatePicker = UIDatePicker() override func viewDidLoad() { super.viewDidLoad() setupUI() loadSavedSettings() } private func setupUI() { // 配置时间选择器为仅显示时间 startDatePicker.datePickerMode = .time endDatePicker.datePickerMode = .time // 添加保存按钮 let saveBtn = UIButton(type: .system) saveBtn.setTitle("保存免打扰设置", for: .normal) saveBtn.addTarget(self, action: #selector(saveSettings), for: .touchUpInside) // 布局(这里省略AutoLayout代码,根据你的项目需求实现) view.addSubview(startDatePicker) view.addSubview(endDatePicker) view.addSubview(saveBtn) } private func loadSavedSettings() { let (startHour, startMinute, endHour, endMinute) = getDoNotDisturbPeriod() let calendar = Calendar.current // 加载开始时间 if let h = startHour, let m = startMinute { var components = DateComponents() components.hour = h components.minute = m startDatePicker.date = calendar.date(from: components) ?? Date() } // 加载结束时间 if let h = endHour, let m = endMinute { var components = DateComponents() components.hour = h components.minute = m endDatePicker.date = calendar.date(from: components) ?? Date() } } @objc private func saveSettings() { let calendar = Calendar.current let startComponents = calendar.dateComponents([.hour, .minute], from: startDatePicker.date) let endComponents = calendar.dateComponents([.hour, .minute], from: endDatePicker.date) guard let startH = startComponents.hour, let startM = startComponents.minute, let endH = endComponents.hour, let endM = endComponents.minute else { return } saveDoNotDisturbPeriod(startHour: startH, startMinute: startM, endHour: endH, endMinute: endM) // 提示用户保存成功 let alert = UIAlertController(title: "设置已保存", message: "免打扰时间段将生效", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", style: .default)) present(alert, animated: true) } }
三、额外注意事项
- 后台推送的限制:如果应用在后台,系统会直接展示推送,此时客户端无法通过
willPresent拦截。如果需要完全禁止后台推送展示,需要后端配合:在禁用时间段内停止向APNs发送推送请求。 - 通知权限:确保应用已获取推送权限,启动时可以请求权限:
func requestNotificationPermission() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in if let error = error { print("请求通知权限失败:\(error.localizedDescription)") } } }
- SwiftUI适配:如果用SwiftUI,推荐用
@AppStorage直接绑定时间选择器,简化存储逻辑,比如:
@AppStorage("DNDStartHour") private var startHour = 22 @AppStorage("DNDStartMinute") private var startMinute = 0 // 然后在DatePicker中绑定这些变量
内容的提问来源于stack exchange,提问作者CodeSteger
相关产品推荐
相关产品推荐

