SwiftUI实现书籍发布日自定义时间本地通知技术问询
解决方案:整合书籍发布日期与用户指定时间触发本地通知
核心思路
要实现发布日期当天+用户选定时分的通知触发,关键是将两个日期的组件拆分后合并:
- 从书籍的
PublishedDateAsADate中提取年、月、日组件 - 从用户设置的通知时间中提取时、分组件
- 合并两组组件生成完整的触发日期,再创建通知触发器
- 将书籍名称传递给通知内容,替换固定标题
步骤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() } }
关键注意事项
- 通知唯一性:使用
书名+发布日期时间戳作为通知标识符,避免重复调度同一书籍的通知 - 日期有效性校验:必须确保合并后的触发日期在当前时间之后,避免调度已过期的通知
- 权限检查:调度通知前先判断用户是否开启了通知权限,避免无效操作
- 持久化存储:用
@AppStorage保存用户的通知开关和时间设置,确保App重启后设置不丢失
内容的提问来源于stack exchange,提问作者Kitty
相关产品推荐
相关产品推荐

