使用 MVVM 在 SwiftUI 中呈现警报

Presenting an Alert in SwiftUI using MVVM

我正在尝试使用 SwiftUI 和 MVVM 架构构建应用程序。我想让我的视图在它的 ViewModel 认为有必要时发出警报——比如,当它从模型中获得某种可用的新结果时。因此,假设每当 VM 检测到新结果时,它都会相应地设置其 status

视图模型:

enum Status {
    case idle
    case computing
    case newResultAvailable
}

class MyViewModel: ObservableObject {

    @Published var status = Status.idle

    ...
}

视图:

struct ContentView: View {

    @ObservedObject var vm = MyViewModel()

    @State private var announcingResult = false {
        didSet {
            // reset VM status when alert is dismissed
            if announcingResult == false {
                vm.status = .idle
            }
        }
    }

    var body: some View {
        Text("Hello")
        .alert(isPresented: $announcingResult) {
            Alert(title: Text("There's a new result!"),
                message: nil,
                dismissButton: .default(Text("OK")))
        }
    }
}

Apple 已将 .alert() 修饰符设计为将绑定作为其第一个参数,以便在绑定 属性 变为 true 时显示警报。然后,当警报解除时,绑定 属性 会自动设置为 false.

我的问题是: 当 VM 的 status 变为 .newResultAvailable 时,如何让警报出现?在我看来,这就是 MVVM 应该如何发挥作用,而且感觉非常符合所有 SwiftUI WWDC 演示的精神,但我找不到办法……

这是可能的方法(已测试并适用于 Xcode 11.3+)

struct ContentView: View {

    @ObservedObject var vm = MyViewModel()

    var body: some View {
        let announcingResult = Binding<Bool>(
            get: { self.vm.status == .newResultAvailable },
            set: { _ in self.vm.status = .idle }
        )
        return Text("Hello")
            .alert(isPresented: announcingResult) {
                Alert(title: Text("There's a new result!"),
                    message: nil,
                    dismissButton: .default(Text("OK")))
            }
    }
}

有时也可以使用以下表示法

var body: some View {
    Text("Hello")
        .alert(isPresented: Binding<Bool>(
            get: { self.vm.status == .newResultAvailable },
            set: { _ in self.vm.status = .idle }
        )) {
            Alert(title: Text("There's a new result!"),
                message: nil,
                dismissButton: .default(Text("OK")))
        }
}

backup

我创建了一个助手class

class AlertProvider {
    struct Alert {
        var title: String
        let message: String
        let primaryButtomText: String
        let primaryButtonAction: () -> Void
        let secondaryButtonText: String
    }

    @Published var shouldShowAlert = false

    var alert: Alert? = nil { didSet { shouldShowAlert = alert != nil } }
}

在虚拟机中可以这样使用

var alertProvider = AlertProvider()

func showAlert() {
    alertProvider.alert = AlertProvider.Alert(
        title: "The Locatoin Services are disabled",
        message: "Do you want to turn location on?",
        primaryButtomText: "Go To Settings",
        primaryButtonAction: { [weak self] in
            self?.appSettingsHandler.openAppSettings()
        },
        secondaryButtonText: "Cancel"
    )
}

我们还可以使用警报视图的扩展 class

extension Alert {
    init(_ alert: AlertProvider.Alert) {
        self.init(title: Text(alert.title),
                  message: Text(alert.message),
                  primaryButton: .default(Text(alert.primaryButtomText),
                                          action: alert.primaryButtonAction),
                  secondaryButton: .cancel(Text(alert.secondaryButtonText)))
    }
}

然后View会按照下面的方式使用它

.alert(isPresented: $viewModel.alertProvider.shouldShowAlert ) {
        guard let alert = viewModel.alertProvider.alert else { fatalError("Alert not available") }

        return Alert(alert)
}

我相信这种方法可以进一步改进