使用 MVVM 在 SwiftUI 中显示警报

Ant*_*ton 5 alert mvvm ios swift swiftui

我正在尝试使用 SwiftUI 和 MVVM 架构构建一个应用程序。我想让我的视图在它的 ViewModel 认为有必要的时候显示一个警报——比如,当它有一个来自模型的新结果时。因此,假设每当 VM 检测到新结果时,它会status相应地设置:

视图模型:

enum Status {
    case idle
    case computing
    case newResultAvailable
}

class MyViewModel: ObservableObject {

    @Published var status = Status.idle

    ...
}
Run Code Online (Sandbox Code Playgroud)

风景:

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")))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我的问题是: 当 VMstatus变为时,我怎样才能让警报出现.newResultAvailable?在我看来,这就是 MVVM 应该如何正确运行,这与所有 SwiftUI WWDC 演示的精神非常相似,但我找不到方法……

Asp*_*eri 7

这是可能的方法(经过测试并与 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")))
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

有时,以下符号可能更可取

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")))
        }
}
Run Code Online (Sandbox Code Playgroud)


KIO*_*KIO 5

我创建了一个助手类

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 } }
}
Run Code Online (Sandbox Code Playgroud)

在VM中它可以这样使用

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"
    )
}
Run Code Online (Sandbox Code Playgroud)

我们还可以使用 Alert View 类的扩展

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)))
    }
}
Run Code Online (Sandbox Code Playgroud)

然后视图会以下列方式使用它

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

        return Alert(alert)
}
Run Code Online (Sandbox Code Playgroud)

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