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

SwiftUI实现书籍发布日自定义时间本地通知技术问询

解决方案:整合书籍发布日期与用户指定时间触发本地通知

核心思路

要实现发布日期当天+用户选定时分的通知触发,关键是将两个日期的组件拆分后合并:

  1. 从书籍的PublishedDateAsADate中提取年、月、日组件
  2. 从用户设置的通知时间中提取时、分组件
  3. 合并两组组件生成完整的触发日期,再创建通知触发器
  4. 将书籍名称传递给通知内容,替换固定标题

步骤1:重构通知管理逻辑(解耦视图与业务)

创建全局单例的通知管理器,统一处理权限请求、通知调度和用户设置存储,避免视图间状态传递混乱:

import SwiftUI
import UserNotifications

class NotificationManager: ObservableObject {
    static let shared = NotificationManager()
    
    // 用AppStorage持久化用户设置
    @AppStorage("showAlertOn") var showAlertOn = false
    @AppStorage("releaseNotificationTime") var releaseNotificationTime = Date()
    
    // 请求通知权限
    func requestAuthorization() {
        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
        UNUserNotificationCenter.current().requestAuthorization(options: options) { success, error in
            if let error = error {
                print("权限请求失败: \(error)")
            }
        }
    }
    
    // 调度书籍发布通知
    func scheduleNotification(bookTitle: String, publishDate: Date) {
        // 权限未开启直接返回
        guard showAlertOn else { return }
        
        // 构建通知内容
        let content = UNMutableNotificationContent()
        content.title = bookTitle
        content.subtitle = "新书已发布"
        content.sound = UNNotificationSound.default
        
        // 拆分并合并日期组件
        let publishDateComps = Calendar.current.dateComponents([.year, .month, .day], from: publishDate)
        let timeComps = Calendar.current.dateComponents([.hour, .minute], from: releaseNotificationTime)
        
        var triggerComps = DateComponents()
        triggerComps.year = publishDateComps.year
        triggerComps.month = publishDateComps.month
        triggerComps.day = publishDateComps.day
        triggerComps.hour = timeComps.hour
        triggerComps.minute = timeComps.minute
        
        // 验证触发日期是否有效(必须在当前时间之后)
        guard let triggerDate = Calendar.current.date(from: triggerComps), triggerDate > Date() else {
            print("无效的触发日期:发布日期已过期或时间组件错误")
            return
        }
        
        // 创建唯一标识符,避免重复调度同一书籍的通知
        let requestID = "\(bookTitle)-\(publishDate.timeIntervalSince1970)"
        let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComps, repeats: false)
        let request = UNNotificationRequest(identifier: requestID, content: content, trigger: trigger)
        
        // 添加通知请求
        UNUserNotificationCenter.current().add(request) { error in
            error.map { print("通知调度失败: \($0)") }
        }
    }
}

步骤2:更新SettingsView,使用全局通知管理器

将原SettingsView中的状态和方法替换为调用通知管理器,确保用户设置持久化:

struct SettingsView: View {
    @Environment(\.dismiss) var dismiss
    @StateObject private var notificationManager = NotificationManager.shared
    
    var body: some View {
        NavigationStack{
            Form {
                Section {
                    Toggle("允许通知", systemImage: notificationManager.showAlertOn ? "bell.fill" : "bell.badge", isOn: $notificationManager.showAlertOn)
                        .toggleStyle(.switch)
                        .tint(.pink)
                        .contentTransition(.symbolEffect)
                        .onChange(of: notificationManager.showAlertOn) { _ in
                            notificationManager.requestAuthorization()
                        }
                    
                    DatePicker("选择通知发送的时分", selection: $notificationManager.releaseNotificationTime, displayedComponents: .hourAndMinute)
                        .padding()
                        .background(.thinMaterial)
                        .cornerRadius(10)
                    
                    // 可选:测试通知调度
                    Button ("测试通知") {
                        notificationManager.scheduleNotification(bookTitle: "测试书籍", publishDate: Date().addingTimeInterval(3600))
                    }
                } header: {
                    Text("通知设置").bold().font(.system(size: 20, design: .rounded))
                }
                
                Section {
                    Text ("导入: 支持booktracker格式文件")
                    Text ("导出: csv、pdf格式")
                }
            }
            Text("选择应用图标")
                .toolbar {
                    Button ("关闭") {
                        dismiss()
                    }
                }
        }
    }
}

步骤3:在BookView中调用通知调度

在书籍详情页添加触发通知的入口(比如按钮),传递书籍名称和发布日期给管理器:

struct BookView: View {
    let book: Book // 你的书籍模型
    @StateObject private var notificationManager = NotificationManager.shared
    private var viewModel: BookViewModel // 你的书籍视图模型
    
    // 原有的日期相关属性
    private var PublishedDate: String { book.volumeInfo.publishedDate ?? "未知"}
    private var PublishedDateAsADate: Date { viewModel.dateStringToDate(dateString: PublishedDate) ?? Date.now.addingTimeInterval(-86400)}
    private var remainingDays: Int {
        Int(viewModel.daysTo(date: PublishedDateAsADate) ?? -2)
    }

    var body: some View {
        VStack(spacing: 16) {
            Text("发布日期: \(PublishedDate)")
            Text("剩余天数: \(remainingDays)")
            
            // 仅当书籍未发布时显示通知按钮
            if remainingDays >= 0 {
                Button("设置发布通知") {
                    notificationManager.scheduleNotification(
                        bookTitle: book.volumeInfo.title ?? "未知书籍",
                        publishDate: PublishedDateAsADate
                    )
                }
                .disabled(!notificationManager.showAlertOn)
                .buttonStyle(.borderedProminent)
            }
        }
        .padding()
    }
}

关键注意事项

  1. 通知唯一性:使用书名+发布日期时间戳作为通知标识符,避免重复调度同一书籍的通知
  2. 日期有效性校验:必须确保合并后的触发日期在当前时间之后,避免调度已过期的通知
  3. 权限检查:调度通知前先判断用户是否开启了通知权限,避免无效操作
  4. 持久化存储:用@AppStorage保存用户的通知开关和时间设置,确保App重启后设置不丢失

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:57:36