iosswiftmvvmswiftuialert

Presenting an Alert in SwiftUI using MVVM


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…


Solution

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