Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dismiss view from view model [MODAL PAGE]

I'm using swiftUI and combine, I'have some business logic in my VM. Some results have to dismiss my view.

I'v used this one in some views :

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

self.presentationMode.wrappedValue.dismiss()

I want to something similar in my view model.

like image 833
theMouk Avatar asked Nov 29 '19 10:11

theMouk


People also ask

How do I dismiss view from Viewmodel SwiftUI?

The first option is to tell the view to dismiss itself using its presentation mode environment key. Any view can read its presentation mode using @Environment(\. presentationMode) , and calling wrappedValue. dismiss() on that will cause the view to be dismissed.

How do I dismiss a popover SwiftUI?

In particular, a user can dismiss a sheet by dragging it down, or a popover by clicking or tapping outside of the presented view.


1 Answers

You don't do the dismissal in imperative way in SwiftUI. Instead you use a .sheet view by binding it to a boolean property that will be mutated from that said view model.

Edit:

After answering a follow-up question, I came up with a different approach. It plays nice if the dismissal is actually needed to be done from inside the modally presented View itself.

You can achieve this by implementing your custom Publisher which will use .send() method to allow you to send specific values to the subscriber (in this case, your View). You will use onReceive(_:perform:) method defined on the View protocol of SwiftUI to subscribe to the output stream of the custom Publisher you defined. Inside the perform action closure where you will have the access to the latest emitted value of your publisher, you will do the actual dismissal of your View.

Enough of the theory, you can look at the code, should not be very hard to follow, below:

import Foundation
import Combine

class ViewModel: ObservableObject {
    var viewDismissalModePublisher = PassthroughSubject<Bool, Never>()
    private var shouldDismissView = false {
        didSet {
            viewDismissalModePublisher.send(shouldDismissView)
        }
    }

    func performBusinessLogic() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.shouldDismissView = true
        }
    }
}

And the views counterparts is:

import SwiftUI

struct ContentView: View {
    @State private var isDetailShown = false
    var body: some View {
        VStack {
            Text("Hello, World!")
            Button(action: {
                self.isDetailShown.toggle()
            }) {
                Text("Present Detail")
            }
        }
        .sheet(isPresented: $isDetailShown) {
            DetailView()
        }
    }
}

struct DetailView: View {
    @ObservedObject var viewModel = ViewModel()
    @Environment(\.presentationMode) private var presentationMode
    var body: some View {
        Text("Detail")
        .navigationBarTitle("Detail", displayMode: .inline)
        .onAppear {
            self.viewModel.performBusinessLogic()
        }
        .onReceive(viewModel.viewDismissalModePublisher) { shouldDismiss in
            if shouldDismiss {
                self.presentationMode.wrappedValue.dismiss()
            }
        }
    }
}

Old Answer:

A very simple implementation of view dismissal with respect to business logic changes in View Model would be:

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    var body: some View {
        Text("Hello, World!")

        // the animation() modifier is optional here
        .sheet(isPresented: $viewModel.isSheetShown.animation()) { 
            Text("Sheet Presented")
        }

        // From here - for illustration purpose
        .onAppear {
            self.viewModel.perform()
        }
        // To here

    }
}

class ViewModel: ObservableObject {
    @Published var isSheetShown = false

    func perform() {
        // this just an example. In real application, you will be responsible to
        // toggle between the states of the `Bool` property
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.isSheetShown.toggle()
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                self.isSheetShown.toggle()
            }
        }
    }
}
like image 65
nayem Avatar answered Sep 22 '22 10:09

nayem