The short answer is that there is NO good way to do this right now.
Here's a few alternatives:
Use a UIKit + Swift Hybrid
See this Present a new view in SwiftUI
Attach a conditional View
The first answer cover this solution. Basically you present you "modal" view on top of or instead of the "base" view using a Bool.
Something like this:
struct ModalView: View {
var closeAction: (() -> Void) = {}
var body: some View {
ZStack {
Color.blue.edgesIgnoringSafeArea(.all)
VStack {
Text("I am a modal.")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.white)
.padding()
Button(action: {
self.closeAction()
}, label: {
Text("OK, BYE!")
.foregroundColor(.white)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.white, lineWidth: 1)
)
})
}
}
}
}
struct BaseView: View {
@State private var showModal = false
var body: some View {
ZStack {
if showModal {
ModalView(closeAction: {
withAnimation(.easeOut(duration: 0.25)) { self.showModal = false }
}).transition(.slideBottom)
} else {
VStack {
Button(action: {
withAnimation(.easeOut(duration: 0.25)) {
self.showModal = true
}
}, label: {
Text("Open Modal")
.padding()
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.blue, lineWidth: 1)
)
})
}
}
}.statusBar(hidden: true)
}
}
It has its own set of issues:
- Not really a modal presentation(?). The "base" view and all its view hierarchy are still present under the "modal". This can be a pain if you want to support accesibility. You can of course present the conditional modal instead of rather than on top of the base view. This setup works but needs to be written rather clever to scale well.
- You will have to add your own animated transition.
- Doesn't work if the base view is embeded in a NavigationView root unless the its navigation bar is hidden
- Doesn't work if the base view is embeded in a NavigationView child, that is a View presented using NavigationLink unless the its navigation bar and back button are hidden
- In general it ceases to be a fullscreen modal when the modifier is added to a View that is not the root View in a given layout.
For a full exploration of this see https://github.com/piterwilson/SwiftUI-Modal-on-iPad/tree/master/iPadConditionalViewModal and this ViewModifier I made to make the code a bit cleaner https://github.com/piterwilson/SwiftUI-FullscreenModalViewModifier
Use NavigationView + NavigationLink
You an also present fullscreen using NavigationView + NavigationLink but the biggest problem is that you won't be able to customize the animation. It looks something like this:
struct ModalView: DismissableView {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("I am a modal.")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.white)
.padding()
Button(action: {
self.dismiss()
}, label: {
Text("OK, BYE!")
.foregroundColor(.white)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.white, lineWidth: 1)
)
})
}
}.navigationBarBackButtonHidden(true)
}
}
struct BaseView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: ModalView()) {
Text("Open Modal")
.padding()
.overlay(
RoundedRectangle(cornerRadius: 5).stroke(Color.blue, lineWidth: 1)
)
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
It also has problems: