How to dismiss SwiftUI modal View embedded in a UIHostingController
Download materialsAdding SwiftUI views into a UIViewController
is very easy using UIHostingController
and encourages us to start using SwiftUI within UIKit apps. Eventually, you'll need to dismiss the view, which can be done in a number of ways.
In this article, let's look at some most obvious ways of dismissing modal SwiftUI views:
- Using SwiftUI
presentationMode
environment value - Using SwiftUI
dismiss
environment value for iOS 15 and above - Providing a dismiss action from the presenting
UIViewController
You can download a sample project on GitHub.
Presenting SwiftUI view
Before looking at the ways we can dismiss a SwiftUI view, let's embed it into a UIHostingController
and present it from a UIViewController
. We will also add a "gear" navigation bar button that triggers the presentation:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configureViewController()
}
private func configureViewController() {
title = "Dismiss SwiftUI Modal View"
configureNavigationBar()
}
private func configureNavigationBar() {
let presentSettingsAction = UIAction { _ in
self.presentSwiftUISettingsScreen()
}
let settingsButton = UIBarButtonItem(
image: UIImage(systemName: "gear"),
primaryAction: presentSettingsAction
)
navigationItem.rightBarButtonItem = settingsButton
}
private func presentSwiftUISettingsScreen() {
let settingsScreen = SettingsScreen()
let hostingController = UIHostingController(rootView: settingsScreen)
present(hostingController, animated: true, completion: nil)
}
}
Once the view is presented, let's add a dismiss button to it.
Using SwiftUI presentationMode environment value
The best way to dismiss the SwiftUI
view is to use presentationMode
environment value. The benefit of using presentationMode
is that it works for any presentation flow: pushing onto a navigation stack and displaying as a modal screen.
Dismissing the SwiftUI view using environment value works from any presentation context: when presenting from the SwiftUI view and from UIKit.
The implementation is very straightforward. In your SwiftUI view add environment value and call its dismiss method when needed (for example, in the navigation bar button):
struct SettingsScreen: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Dismiss") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
}
}
This is a very nice implementation and a recommended approach. However, in iOS 15 we get another, direct API.
Using SwiftUI dismiss environment value
If you are targeting iOS 15 and above, then you can use even more direct dismiss
action from SwiftUI View:
@available(iOS 15.0, *)
struct SettingsScreen: View {
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Dismiss") {
dismiss()
}
}
}
}
}
}
Providing a dismiss action from the presenting UIViewController
Another approach that is commonly used is to pass a dismiss action when you are presenting a SwiftUI View.
You simply add a dismiss property to your SwiftUI view and call it in a Button's action:
struct SettingsScreen: View {
var dismissAction: (() -> Void)
var body: some View {
NavigationView {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Dismiss") {
dismissAction()
}
}
}
}
}
}
When you are presenting the View from UIKit
, provide a dismiss action:
private func presentSwiftUISettingsScreen() {
let settingsScreen = SettingsScreen(
dismissAction: {
self.dismiss(animated: true, completion: nil)
}
)
let hostingController = UIHostingController(rootView: settingsScreen)
present(hostingController, animated: true, completion: nil)
}
This approach is very similar to the pattern that is used in UIKit and can be used for any kind of action.
Conclusion
In this article, you've seen how to dismiss a SwiftUI view using different approaches. I would recommend using environment values as it is a native SwiftUI implementation. However, I wanted to show the last approach as a reference that can be used to trigger other basic actions that you have already implemented in a UIViewController and want to reuse from the SwiftUI context.
You can download a sample project with the implementation via the link at the top of the page.
I hope you enjoyed this article. If you have any questions, suggestions, or feedback, please let me know on Twitter.
Thanks for reading!