Dismissing Presented Content in SwiftUI

Posted in SwiftUI

October 9th, 2023

⏱ Reading Time: 4 mins

Almost all apps present some sort of modal content, meaning content that is displayed on top of other content. For instance, sheets and popovers are system provided controls whose content is presented modally. It’s also quite common to show content by pushing views in a navigation stack, as well as to pop new windows that users can interact with on macOS.

No matter how the new content is presented -modally, pushed, or somewhat else- there is always one thing that remains constant. Users must be able to dismiss it. Although there are system-provided ways to do so most of the times, often developers include their own controls to make this possible as well.

In this post you are going to meet how to manage exactly that, starting with the recommended approach as of iOS 15 and onwards. For reasons of completeness, I’ll also cover how that was possible before iOS 15, and lastly, I’ll also mention a common, more manual and less recommended way to dismiss presented content.

Dismissing presented content

For the sake of the example, suppose that we have two SwiftUI views where the first one presents the second in a sheet. In the code example that follows, the showSheet state property in ContentView determines when the sheet shows up modally. Every time the button in the ContentView is pressed, showSheet becomes true and the sheet appears presenting the contents of the ModalView.

Even though we can slide down to dismiss the sheet, the purpose is to make the button in ModalView initiate the dismissal. SwiftUI simplifies that task, as it provides us with a dismiss action that’s accessible as an environment value.

In particular, that environment value is called dismiss, and it’s an instance of a special type named DismissAction. The first step towards hiding the presented view is to declare such an environment property in it:

The second and last step is to invoke dismiss as a function in any place that’s necessary to perform a dismiss action. In this example, that’s the button’s action closure in ModalView:

Note that dismiss is suffixed with parentheses; even though we declare it as a property, we call it as a function. That’s possible thanks to a particular method defined in the DismissAction type, named callAsFunction(). I won’t get into details about it here, that’s the topic of a different tutorial.

The above two additions is all you need in every view that provides custom controls to dismiss it. Just make sure that you use the dismiss environment object in the presented view, not the presenting one.

An animated gif that presents a sheet modally on button tap. Modal is dismissed by tapping on another button.

💡 Tip: If you perform dismiss actions often, then you can make things a bit more faster by turning the @Environment(\.dismiss) private var dismiss declaration into a custom code snippet.

Dismissing views before iOS 15

If you’re aiming on supporting iOS versions prior to 15, then you should resort in using a different (nowadays deprecated) API, yet similar to what already demonstrated above. Specifically, it’s another environment value that SwiftUI makes available as shown right next:

To dismiss the presented view using the presentationMode environment value all we need is to call a dismiss() method. That’s accessible through the wrappedValue of presentationMode:

Here’s ModalView from the previous part making use of presentationMode this time:

Dismissing views using a binding value

Besides everything else already demonstrated in the previous parts, there is also another technique to dismiss modal views. It’s based on passing a binding to the state property that controls the appearance of the modal from the presenting to the presented view.

Being more precise, let’s see once again the ModalView from the previous examples, only this time there are no environment values at all. Instead, we declare a binding property that will trigger the disappearance of the view:

Notice that showSheet becomes false in the action closure of the button, and that’s where the dismissal of the view is initiated.

The original binding value is provided as argument by the view that presents the modal, which in this case is the ContentView. In the following updated version of it, you can see that the ModalView instance is initialized and gets as argument the binding value of the showSheet state property; the one that determines whether the modal view should appear or not:

When showSheet becomes true in the ContentView, then the ModalView appears. Later, when it becomes false in ModalView, then the latter is dismissed.

Although the method showcased in this part works fine, it’s not the recommended way to dismiss modal views. There are two reasons for that. The first is that SwiftUI already provides an environment value to perform a dismiss action, so we can avoid taking care of that manually. And that brings us to the second reason; passing a binding value as argument is a totally unnecessary step. But at the bottom end, it’s up to you to decide if you’re more fond of this way, even if it includes an additional step in the development workflow.

Conclusion

SwiftUI provides us with the needed tools to dismiss presented content, either that’s a modal or a pushed view in a navigation stack. The environment value you’ll use depends on the system version you’re targeting to, with the dismiss action being the way to go since iOS 15. Dismissing presented views is also possible by passing binding values that toggle their appearance, but as already said in the last part, it shouldn’t be the preferred method just because it works. In any case, it’s all up to you to pick your tools.

I hope you found this post valuable. Thanks for reading!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.