Presenting an Alert in SwiftUI using MVVM

泪湿孤枕 提交于 2021-02-07 20:13:50

问题


I'm trying to build an app using SwiftUI and an MVVM architecture. I'd like to have my View present an alert whenever its ViewModel deems it necessary—say, when it has a new result of some sort available from the Model. So suppose whenever the VM detects a new result it sets its status accordingly:

The ViewModel:

enum Status {
    case idle
    case computing
    case newResultAvailable
}

class MyViewModel: ObservableObject {

    @Published var status = Status.idle

    ...
}

The View:

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 has designed the .alert() modifier to take a binding as its first argument, so that the alert is displayed whenever the bound property becomes true. Then, when the alert is dismissed, the bound property is automatically set to false.

My question is: How can I have the alert appear whenever the VM's status becomes .newResultAvailable? It seems to me that that's how proper MVVM should function, and it feels very much in the spirit of all the SwiftUI WWDC demos, but I can't find a way…


回答1:


Here is possible approach (tested & works with 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")))
            }
    }
}

also sometimes the following notation can be preferable

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")))
        }
}



回答2:


I have created a helper 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 } }
}

In the VM it can be used like this

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"
    )
}

We can also use an extension for the Alert View 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)))
    }
}

Then the View would use it in the following way

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

        return Alert(alert)
}

I believe further improvements can be made in this approach



来源:https://stackoverflow.com/questions/60102432/presenting-an-alert-in-swiftui-using-mvvm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!