Dismiss view from view model [MODAL PAGE]
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 presentedView
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()
}
}
}
}
Method for when the modal view has been dismissed
UIViewController has a property called parentViewController
. In the case that a view controller is presented modally, the parentViewController
property points to the view controller that presented the modal view controller.
In your modal view controller, in viewWillDisappear:
you can send a message to the parentViewController
to perform any action you wish, essentially.
Something like:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.parentViewController doSomething];
}
If your parent view controller is a table view controller, then you should be able to call [self.parentViewController.tableView reloadData];
to do what you're trying to achieve.
Dismiss current modal view controller and then present new modal view controller
I will try to give a more through answer here. When programming in iOS all instances must be created from... other instances. When I say instance I mean an object that has been instantiated (an object that has been created in the computers memory). So, whenever you make a project with XCode, you always need to mark 'the initial view controller'. This is going to be the first instance that you create. Then it is used to spawn other instances. The reason why Apple chose this architecture is for security reasons (I think...? someone correct me if they have a better answer). You can see a very clear view of all the 'instances' of the views. You click this button while you are running a program in XCode . Then you can see the hierarchy of the views. I have made a simple program where clicking a button will load a different view. Where here I have not clicked the button and I have only loaded one view. However here I have clicked the button and loaded the next view, . At the top they both say UIWindow. That is because... (I hope you can guess this part!) They are both being instantiated from the UIWindow View. Don't believe me? Check this out! Here are the actual view hierarchies. and . So, in the case where you dont get what my point is. You should understand that simply by reading your question it is pretty obvious that you are trying to instantiate your model controller from another one that you are trying to dismiss. So if the model controller has been dismissed how is it supposed to instantiate another model controller? Also keep in mind that it is better to just name your controllers after their purpose like, MenuViewController, or MainController, or VideoController. The word model is usually used in MVC and it should not be used as part of a ViewControllers name. (Hope Im not sounding rude lol, I used to tutor junior high so this is how I teach XD )
Related Topics
Set a Variable to the < ("Less Than") Operator as a Function in Swift
How to Stop a Dispatchqueue in Swift
Why I Can Not Use Equatable Function in My View When the View Can Use It in Swiftui
How to Reduce the Opacity of the Shadows in Realitykit
Why Strings Are Not Equal in My Case
Expression Pattern of Type 'String' Cannot Match Values of Type 'Nsstoryboardsegue.Identifier
Swift iOS 9: Section Header Change Position After Reload Data
Swift 2, Protocol Extensions & Respondstoselector
Convert [(Key: String, Value: String)] in [String:String]
Global Var VS Shared Instance Swift
How to Rotate an Object Around Only One Axis in Realitykit
Cannot Invoke Initializer for Type 'Sqlite3_Destructor_Type'
Writing Data to an Outputstream with Swift 5+
How to Prompt for Accessibility Features in a MACos App (From the Appdelegate)
Swift - Encode and Decode a Dictionary [String:Any] into Plist
How to Pass a Nil Value for One of the Parameter in Alamofire Post Request
Exc_Bad_Instruction Happens When Using Dispatch_Get_Global_Queue on iOS 7(Swift)