修改iOS后台通知:9点提醒未领取每日免费宝石的用户
每日宝石领取9点提醒实现方案
核心逻辑明确
要实现的效果是:每天9点仅当用户未领取当日宝石时,发送领取提醒通知。核心判断条件有两个:
- 当前时间为9点整
- 用户当日未领取宝石(
lastClaimDate数组中无今日日期记录)
具体实现步骤及代码修正
1. 注册每日9点的通知触发
用UNCalendarNotificationTrigger设置重复触发的每日9点通知,同时避免重复注册:
import UserNotifications func setupDaily9AMGemReminder() { let notificationCenter = UNUserNotificationCenter.current() // 先移除旧的同ID通知,防止重复 notificationCenter.removePendingNotificationRequests(withIdentifiers: ["DailyGemClaimReminder"]) let content = UNMutableNotificationContent() content.title = "宝石待领取" content.body = "今日免费宝石还没领,快上线领取吧!" content.sound = .default content.badge = 1 // 设置每天9点的触发规则 var dateComponents = DateComponents() dateComponents.hour = 9 dateComponents.minute = 0 let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) let request = UNNotificationRequest(identifier: "DailyGemClaimReminder", content: content, trigger: trigger) notificationCenter.add(request) { error in if let err = error { print("通知注册失败:\(err.localizedDescription)") } } }
2. 通知触发前的实时判断(关键修正)
之前的问题在于没有在通知展示前做领取状态校验,导致无论是否领取都触发。现在通过UNUserNotificationCenterDelegate的方法拦截通知,判断后再决定是否展示:
extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { // 只处理宝石提醒通知 guard notification.request.identifier == "DailyGemClaimReminder" else { completionHandler([.banner, .sound]) return } // 校验今日是否已领取 let today = Calendar.current.startOfDay(for: Date()) let savedDates = UserDefaults.standard.array(forKey: "lastClaimDate") as? [Date] ?? [] let hasClaimedToday = savedDates.contains { Calendar.current.isDate($0, inSameDayAs: today) } if hasClaimedToday { // 已领取,不展示通知 completionHandler([]) } else { // 未领取,展示通知+角标 completionHandler([.banner, .sound, .badge]) } } // 处理通知点击跳转 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { if response.notification.request.identifier == "DailyGemClaimReminder" { // 这里可以跳转到宝石领取页面 // 示例: // let claimVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "GemClaimVC") // UIApplication.shared.keyWindow?.rootViewController?.pushViewController(claimVC, animated: true) } completionHandler() } }
3. 领取宝石后的状态更新优化
存储领取日期时,统一存当日的起始时间(避免时分秒差异导致判断错误),同时清理旧记录优化存储:
func claimDailyGems() { let today = Calendar.current.startOfDay(for: Date()) var savedClaimDates = UserDefaults.standard.array(forKey: "lastClaimDate") as? [Date] ?? [] // 移除今日之前的旧记录(可选,减少存储体积) savedClaimDates = savedClaimDates.filter { !Calendar.current.isDate($0, before: today) } // 避免重复添加今日记录 if !savedClaimDates.contains(where: { Calendar.current.isDate($0, inSameDayAs: today) }) { savedClaimDates.append(today) UserDefaults.standard.set(savedClaimDates, forKey: "lastClaimDate") // 清除角标 UIApplication.shared.applicationIconBadgeNumber = 0 } }
4. 应用启动时的初始化工作
在App启动时完成通知权限申请、代理设置和角标更新:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 设置通知代理 UNUserNotificationCenter.current().delegate = self // 申请通知权限 let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, _ in if granted { // 权限通过后注册每日提醒 self.setupDaily9AMGemReminder() } } // 启动时更新角标状态 updateGemClaimBadge() return true } func updateGemClaimBadge() { let today = Calendar.current.startOfDay(for: Date()) let savedDates = UserDefaults.standard.array(forKey: "lastClaimDate") as? [Date] ?? [] let hasClaimed = savedDates.contains { Calendar.current.isDate($0, inSameDayAs: today) } UIApplication.shared.applicationIconBadgeNumber = hasClaimed ? 0 : 1 }
常见问题排查
- 确认通知权限已开启:用户必须允许App发送通知,否则所有通知都无法展示
- 日期判断用统一标准:始终用
Calendar.current.startOfDay(for: Date())获取当日起始时间,避免因时分秒不同导致判断错误 - 避免重复通知:每次注册新通知前,移除旧的同ID通知请求
- 后台任务替代方案:如果依赖后台刷新任务,苹果对其触发频率有严格限制,不如本地通知+实时判断可靠
内容的提问来源于stack exchange,提问作者Nathanael Tse
相关产品推荐
相关产品推荐

