我正在寻找特定问题的解决方案,但也不介意一些一般性建议。我构建的最后一个 iOS 项目是用 Objective-C 编写的。我使用的是针对 iOS 13.3 的 Swift 5.1
我的应用程序严重依赖 UserNotifications。没有经过许可是没有用的。因此,当用户更新我们的提醒实例并触发我的保存功能(保存到磁盘并生成通知)时,我想重新检查他们的权限,并在未授予权限的情况下提示他们访问设置。很简单吧?
嗯,授予权限的提示仅在我第一次请求时出现。因此,我需要对该呼叫的负面结果触发警报。我只是在我的应用程序中更改了 @State 变量,但我在异步上下文中处于 init 闭包中;系统没有。
当我们的用户更新提醒模型并触发下面的保存操作时,我会遇到关于转义闭包捕获变异“self”参数的双倍错误。我得到它;我需要将其包装在我通过引用保存的某个容器对象中。该容器对象可能应该触发我提供的一些闭包?我尝试了一些重构,并开始收到紫色运行时错误,说明如何更改主线程之外的绑定变量不会触发更新(我认为即使在 @MainActor 上,尽管该代码现在已经消失了)。
import SwiftUI
import Combine
import UserNotifications
class NotificationManager: NSObject, UNUserNotificationCenterDelegate { // ObservableObject
static let shared = NotificationManager()
override private init() {}
// #MARK - UNNotificationCenterDelegate methods
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
print(center)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print(response)
completionHandler()
}
}
@main
struct ReminderApp: App {
@ObservedObject private var reminder: Reminder
// @ObservedObject private var notificationManager = NotificationManager.shared
@MainActor @State var shouldRequestAuthorization: Bool = false
private var cancellables = Set<AnyCancellable>()
private var persistAction: (() -> Void)? = nil
init() {
let reminder = Reminder.loadOrCreate()
self._reminder = ObservedObject(wrappedValue: reminder)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.delegate = NotificationManager.shared
notificationCenter.removeAllDeliveredNotifications() // TODO: trigger this more
func requestAuthorization(completion: @escaping (Bool) -> ()) async throws {
// TODO: Not re-appearing after first refusal, link user to settings in Alert..
Task {
let authorized = try await notificationCenter.requestAuthorization(options: [.sound, .alert])
completion(authorized)
}
}
self.persistAction = {
try! reminder.save()
// Escaping closure captures mutating self parameter
Task { @MainActor in
try await requestAuthorization { authorized in
self.shouldRequestAuthorization = authorized
}
}
notificationCenter.removeAllPendingNotificationRequests()
if reminder.enabled {
// Schedule notifications...
}
}
reminder
.objectDidChange
.sink(receiveValue: persistAction!)
.store(in: &cancellables)
//
// notificationManager
// .objectDidChange
// .sink(receiveValue: ...)
// .store(in: &cancellables)
}
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
HomeView()
.environmentObject(reminder)
.padding(Edge.Set(arrayLiteral: [.horizontal, .vertical]), 50)
.alert(isPresented: $shouldRequestAuthorization) {
let settingsButton = Alert.Button.default(Text("Go to Settings")) {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("Settings opened: \(success)") // Prints true
})
}
}
return Alert(title: Text("Notifications Permission Needed"),
message: Text("Our app needs this to send reminders"),
primaryButton: settingsButton,
secondaryButton: .cancel())
}
}
.onChange(of: scenePhase) { phase in
if phase == .background {
// Perform cleanup when all scenes within
// MyApp go to the background.
}
}
}
}
参考资料已经过时,而且问题非常复杂,我认为向你们寻求一点帮助是我最快的选择。这样不是更简单吗?
我很高兴调整我的NotificationsManager以用作我的容器对象(以前这只是称为NotificationsDelegate)
有时你的视野狭隘,你的大脑就会停止运转。休息一下后我回来回答我自己的问题。我将 saveAction 变成了一个函数。呃。我已经意识到 .sink 需要变量名,而不需要方法名。会的。
代码尚未经过充分测试,但我希望这对面临类似问题的任何人都有帮助。
struct ReminderApp: App {
@ObservedObject private var reminder: Reminder
@MainActor @State var shouldRequestAuthorization: Bool = false
private var cancellables = Set<AnyCancellable>()
init() {
let reminder = Reminder.loadOrCreate()
self._reminder = ObservedObject(wrappedValue: reminder)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.delegate = NotificationManager.shared
notificationCenter.removeAllDeliveredNotifications() // TODO: trigger this more
reminder
.objectDidChange
.sink(receiveValue: saveAction)
.store(in: &cancellables)
}
func saveAction() {
try! reminder.save()
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.removeAllPendingNotificationRequests()
// shouldRequestAuthorization = false
Task { //@MainActor in
do {
let authorized = try await notificationCenter.requestAuthorization(options: [.sound, .alert])
shouldRequestAuthorization = !authorized
} catch {
print(error)
#warning("hey")
// fatalError(error)
}
}
if reminder.enabled {
// ...
}
}
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
HomeView()
.environmentObject(reminder)
.padding(Edge.Set(arrayLiteral: [.horizontal, .vertical]), 50)
.alert(isPresented: $shouldRequestAuthorization) {
shouldRequestAuthorization = false
// TODO: encapsulate this
return Alert(title: Text("Notifications Permission Needed"),
message: Text("Our app needs this to send reminders"),
primaryButton: Alert.Button.default(Text("Go to Settings")) {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("Settings opened: \(success)") // Prints true
})
}
},
secondaryButton: .cancel())
}.task {
saveAction()
}
}
.onChange(of: scenePhase) { phase in
if phase == .background {
// Perform cleanup when all scenes within
// MyApp go to the background.
}
}
}
}