如何在 SwiftUI 中显示错误警报?

Rei*_*ner 1 swiftui viewmodifier

设置:

我有一个View可以显示警报的 SwiftUI。警报由AlertManager单例通过设置title和/或message其已发布的属性来提供@Published var nextAlertMessage = ErrorMessage(title: nil, message: nil)。该View拥有财产@State private var presentingAlert = false

当以下修饰符应用于 时,此功能有效View

.onAppear() {
    if !(alertManager.nextAlertMessage.title == nil && alertManager.nextAlertMessage.message == nil) {
        presentingAlert = true
    }
}
.onChange(of: alertManager.nextAlertMessage) { alertMessage in
    presentingAlert = alertMessage.title != nil || alertMessage.message != nil
}
.alert(alertManager.nextAlertMessage.joinedTitle, isPresented: $presentingAlert) {
    Button("OK", role: .cancel) {
        alertManager.alertConfirmed()
    }
}  
Run Code Online (Sandbox Code Playgroud)

问题:

由于警报也将在其他视图中呈现,因此我编写了以下自定义视图修饰符:

struct ShowAlert: ViewModifier {
    
    @Binding var presentingAlert: Bool
    let alertManager = AlertManager.shared
    
    func body(content: Content) -> some View {
        return content
            .onAppear() {
                if !(alertManager.nextAlertMessage.title == nil && alertManager.nextAlertMessage.message == nil) {
                    presentingAlert = true
                }
            }
            .onChange(of: alertManager.nextAlertMessage) { alertMessage in
                presentingAlert = alertMessage.title != nil || alertMessage.message != nil
            }
            .alert(alertManager.nextAlertMessage.joinedTitle, isPresented: $presentingAlert) {
                Button("OK", role: .cancel) {
                    alertManager.alertConfirmed()
                }
            }
    }
}  
Run Code Online (Sandbox Code Playgroud)

并将其应用于View

.modifier(ShowAlert(presentingAlert: $presentingAlert))  
Run Code Online (Sandbox Code Playgroud)

但是,现在没有显示任何警报

问题:

我的代码有什么问题以及如何正确执行?

编辑(根据阿什利·米尔斯的要求):

这是一个最小的可重现示例。
请注意:
在 中ContentView,自定义修饰符ShowAlert已被注释掉。此版本的代码显示了警报。
如果修饰符.onAppear,.onChange.alert被注释掉,并且启用了自定义修饰符,则不会显示警报。

// TestViewModifierApp
import SwiftUI
@main
struct TestViewModifierApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// ContentView
import SwiftUI
struct ContentView: View {
    @ObservedObject var alertManager = AlertManager.shared
    @State private var presentingAlert = false
    var body: some View {
        let alertManager = AlertManager.shared
        let _ = alertManager.showNextAlertMessage(title: "Title", message: "Message")
        Text("Hello, world!")
//          .modifier(ShowAlert(presentingAlert: $presentingAlert))
            .onAppear() {
                if !(alertManager.nextAlertMessage.title == nil && alertManager.nextAlertMessage.message == nil) {
                    presentingAlert = true
                }
            }
            .onChange(of: alertManager.nextAlertMessage) { alertMessage in
                presentingAlert = alertMessage.title != nil || alertMessage.message != nil
            }
            .alert(alertManager.nextAlertMessage.joinedTitle, isPresented: $presentingAlert) {
                Button("OK", role: .cancel) {
                    alertManager.alertConfirmed()
                }
            }
    }
}

// AlertManager
import SwiftUI

struct ErrorMessage: Equatable {
    let title: String?
    let message: String?
    var joinedTitle: String {
        (title ?? "") + "\n\n" + (message ?? "")
    }
    
    static func == (lhs: ErrorMessage, rhs: ErrorMessage) -> Bool {
        lhs.title == rhs.title && lhs.message == rhs.message
    }
}

final class AlertManager: NSObject, ObservableObject {
    static let shared = AlertManager() // Instantiate the singleton
    @Published var nextAlertMessage = ErrorMessage(title: nil, message: nil)
    
    func showNextAlertMessage(title: String?, message: String?) {
        DispatchQueue.main.async {
            // Publishing is only allowed from the main thread
            self.nextAlertMessage = ErrorMessage(title: title, message: message)
        }
    }
    
    func alertConfirmed() {
        showNextAlertMessage(title: nil, message: nil)
    }
}

// ShowAlert
import SwiftUI
struct ShowAlert: ViewModifier {
    @Binding var presentingAlert: Bool
    let alertManager = AlertManager.shared
    
    func body(content: Content) -> some View {
        return content
            .onAppear() {
                if !(alertManager.nextAlertMessage.title == nil && alertManager.nextAlertMessage.message == nil) {
                    presentingAlert = true
                }
            }
            .onChange(of: alertManager.nextAlertMessage) { alertMessage in
                presentingAlert = alertMessage.title != nil || alertMessage.message != nil
            }
            .alert(alertManager.nextAlertMessage.joinedTitle, isPresented: $presentingAlert) {
                Button("OK", role: .cancel) {
                    alertManager.alertConfirmed()
                }
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ash*_*lls 12

你太复杂了,呈现错误警报的方法如下:

  1. 定义一个符合LocalizedError. 最简单的方法是使用enum,为您的应用程序可能遇到的每个错误提供一个案例。您必须实施var errorDescription: String?,这将显示为警报标题。如果您想显示警报消息,请向枚举添加一个方法以返回该消息。
enum MyError: LocalizedError {
    
    case basic
    
    var errorDescription: String? {
        switch self {
        case .basic:
            return "Title"
        }
    }
    
    var errorMessage: String? {
        switch self {
        case .basic:
            return "Message"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 您需要一个@State变量来保存错误,并需要一个在应该显示警报时设置的变量。你可以这样做:
@State private var error: MyError?
@State private var isShowingError: Bool
Run Code Online (Sandbox Code Playgroud)

但是这样你就有了两个事实来源,你必须记住每次都设置两个。或者,您可以使用计算属性Bool

var isShowingError: Binding<Bool> {
    Binding {
        error != nil
    } set: { _ in
        error = nil
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 要显示警报,请使用以下修饰符:
.alert(isPresented: isShowingError, error: error) { error in
    // If you want buttons other than OK, add here
} message: { error in
    if let message = error.errorMessage {
        Text(message)
    }
}
Run Code Online (Sandbox Code Playgroud)

4. 额外学分

正如您上面所做的那样,我们可以将一堆这些东西移动到 a 中ViewModifier,所以我们最终得到:

enum MyError: LocalizedError {
    
    case basic
    
    var errorDescription: String? {
        switch self {
        case .basic:
            return "Title"
        }
    }
    
    var errorMessage: String? {
        switch self {
        case .basic:
            return "Message"
        }
    }
}

struct ErrorAlert: ViewModifier {
    
    @Binding var error: MyError?
    var isShowingError: Binding<Bool> {
        Binding {
            error != nil
        } set: { _ in
            error = nil
        }
    }
    
    func body(content: Content) -> some View {
        content
            .alert(isPresented: isShowingError, error: error) { _ in
            } message: { error in
                if let message = error.errorMessage {
                    Text(message)
                }
            }
    }
}

extension View {
    func errorAlert(_ error: Binding<MyError?>) -> some View {
        self.modifier(ErrorAlert(error: error))
    }
}
Run Code Online (Sandbox Code Playgroud)

现在要显示错误,我们需要的是:

struct ContentView: View {
    
    @State private var error: MyError? = .basic
    
    var body: some View {
        Text("Hello, world!")
            .errorAlert($error)
    }    
}
Run Code Online (Sandbox Code Playgroud)