如何从应用程序触发 SwiftUI 警报以响应异步查询(用户通知授权)

问题描述 投票:0回答:1

我正在寻找特定问题的解决方案,但也不介意一些一般性建议。我构建的最后一个 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)

swift testing swiftui concurrency swift5
1个回答
0
投票

有时你的视野狭隘,你的大脑就会停止运转。休息一下后我回来回答我自己的问题。我将 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.
            }
        }
    }

}
© www.soinside.com 2019 - 2024. All rights reserved.