You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 00:43:19