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

如何实现按单双日推送本地通知并向服务器回传确认?

技术咨询:如何实现按单双日推送本地通知并向服务器回传确认信息?

背景说明

我已在提醒功能(基于EventKit)中实现过类似的单双日提醒逻辑,具体代码如下:

/// Creates a reminder
func create(_ title: String, isOddDays: Bool, hour: Int, minute: Int) {
    let dueDate = Date()
    let gregorian = Calendar(identifier: .gregorian)
    
    let daysOfTheMonth: [NSNumber]
    if isOddDays {
        daysOfTheMonth = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31]
    } else {
        daysOfTheMonth = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
    }
    
    let rule = EKRecurrenceRule(
        recurrenceWith: .monthly,
        interval: 1,
        daysOfTheWeek: nil,
        daysOfTheMonth: daysOfTheMonth,
        monthsOfTheYear: nil,
        weeksOfTheYear: nil,
        daysOfTheYear: nil,
        setPositions: nil,
        end: nil
    )
    
    guard let reminder: EKReminder = self.setUpReminder(with: title, priority: 0) else {
        return
    }
    
    reminder.dueDateComponents = gregorian.dateComponents([.day, .month, .year, .hour, .minute, .second], from: dueDate)
    reminder.addRecurrenceRule(rule)
    
    let todayDateComponents = gregorian.dateComponents([.day, .month, .year], from: Date())
    let day: Int = isOddDays ? 1 : 2
    let month: Int = todayDateComponents.month!
    let year: Int = todayDateComponents.year!
    
    let absoluteDate = Date.create(day: day, month: month, year: year, hour: hour, minute: minute, second: 0)
    
    let alarm = EKAlarm(absoluteDate: absoluteDate)
    reminder.addAlarm(alarm)
    
    UserDefaults.standard.set(reminder.calendarItemIdentifier, forKey: ReminderStoreManager.reminderDefaultsKey)
    
    self.saveReminder(reminder)
}

现希望用**本地通知(UserNotifications框架)**实现类似的单双日推送逻辑,完成后向服务器回传确认信息。

编辑补充:已添加本地通知配置界面截图(显示通知权限、触发时间等设置项)


实现方案

一、核心思路

由于UserNotifications不支持直接指定每月多日期的重复规则,采用「单次调度+触发后自动续期」的方式:

  1. 首次调度时计算下一个符合单双日条件的日期,创建单次本地通知
  2. 通知触发/用户点击后,立即调度下一个符合条件的通知
  3. 同时在触发回调中向服务器发送确认请求

二、具体实现步骤

1. 请求通知权限

先获取用户的本地通知权限:

import UserNotifications

func requestNotificationPermission() {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
        if granted {
            print("通知权限已获取")
        } else {
            print("通知权限被拒绝")
        }
    }
}

2. 首次调度单双日通知

计算下一个符合条件的日期,创建单次本地通知:

func scheduleOddEvenDayNotification(title: String, isOddDays: Bool, hour: Int, minute: Int) {
    // 构建通知内容
    let content = UNMutableNotificationContent()
    content.title = title
    content.body = "单双日提醒"
    content.sound = UNNotificationSound.default
    
    let gregorian = Calendar(identifier: .gregorian)
    // 找到下一个符合单双日条件的日期
    var nextDate = Date()
    repeat {
        nextDate = gregorian.date(byAdding: .day, value: 1, to: nextDate)!
        let day = gregorian.component(.day, from: nextDate)
    } while (isOddDays && day % 2 == 0) || (!isOddDays && day % 2 != 0)
    
    // 创建触发条件
    let triggerComponents = gregorian.dateComponents([.year, .month, .day, .hour, .minute], from: nextDate)
    let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: false)
    
    // 创建通知请求
    let requestID = isOddDays ? "odd_day_notification" : "even_day_notification"
    let request = UNNotificationRequest(identifier: requestID, content: content, trigger: trigger)
    
    // 添加到通知中心
    UNUserNotificationCenter.current().add(request) { error in
        if let error = error {
            print("添加通知失败:\(error.localizedDescription)")
        }
    }
    
    // 保存用户设置到UserDefaults,用于后续续期
    UserDefaults.standard.set(isOddDays, forKey: "isOddDayNotification")
    UserDefaults.standard.set(hour, forKey: "notificationHour")
    UserDefaults.standard.set(minute, forKey: "notificationMinute")
}

3. 处理通知触发与服务器回传

设置通知中心代理,在通知触发/用户点击时完成续期和回传:

// 在AppDelegate中设置代理
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    requestNotificationPermission()
    checkAndRescheduleNotification() // App启动时检查并补全通知
    return true
}

// 实现UNUserNotificationCenterDelegate协议
extension AppDelegate: UNUserNotificationCenterDelegate {
    // 前台收到通知时的处理
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // 显示通知
        completionHandler([.alert, .sound])
        
        // 回传服务器确认
        sendConfirmationToServer(notificationID: notification.request.identifier)
        
        // 续期下一个通知
        rescheduleNextNotification(from: notification.request.identifier)
    }
    
    // 用户点击通知后的处理
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // 回传服务器确认
        sendConfirmationToServer(notificationID: response.notification.request.identifier)
        
        // 续期下一个通知
        rescheduleNextNotification(from: response.notification.request.identifier)
        
        completionHandler()
    }
    
    // 续期下一个符合条件的通知
    func rescheduleNextNotification(from requestID: String) {
        let isOddDays = requestID == "odd_day_notification"
        let hour = UserDefaults.standard.integer(forKey: "notificationHour")
        let minute = UserDefaults.standard.integer(forKey: "notificationMinute")
        
        scheduleOddEvenDayNotification(title: isOddDays ? "奇数日提醒" : "偶数日提醒", isOddDays: isOddDays, hour: hour, minute: minute)
    }
    
    // 向服务器发送确认请求
    func sendConfirmationToServer(notificationID: String) {
        guard let url = URL(string: "https://your-server-api.com/confirm-notification") else { return }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        // 构建请求参数,替换为实际业务字段
        let params: [String: Any] = [
            "notification_id": notificationID,
            "user_id": "current_user_id",
            "trigger_time": Date().timeIntervalSince1970
        ]
        
        do {
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: [])
        } catch {
            print("参数序列化失败:\(error.localizedDescription)")
            return
        }
        
        // 发送请求
        URLSession.shared.dataTask(with: request) { _, response, error in
            if let error = error {
                print("回传失败:\(error.localizedDescription)")
                return
            }
            
            if let httpResp = response as? HTTPURLResponse, httpResp.statusCode == 200 {
                print("回传确认成功")
            } else {
                print("回传失败,状态码异常")
            }
        }.resume()
    }
}

4. App启动时补全通知

避免App被杀死后通知中断,启动时检查并重新调度:

func checkAndRescheduleNotification() {
    UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
        let isOddDays = UserDefaults.standard.bool(forKey: "isOddDayNotification")
        let targetID = isOddDays ? "odd_day_notification" : "even_day_notification"
        
        let hasScheduled = requests.contains { $0.identifier == targetID }
        if !hasScheduled {
            let hour = UserDefaults.standard.integer(forKey: "notificationHour")
            let minute = UserDefaults.standard.integer(forKey: "notificationMinute")
            self.scheduleOddEvenDayNotification(title: isOddDays ? "奇数日提醒" : "偶数日提醒", isOddDays: isOddDays, hour: hour, minute: minute)
        }
    }
}

内容的提问来源于stack exchange,提问作者Idan Moshe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 22:00:56