Present a new view in SwiftUI
To show a modal (iOS 13 style)
You just need a simple sheet
with the ability to dismiss itself:
struct ModalView: View { @Binding var presentedAsModal: Bool var body: some View { Button("dismiss") { self.presentedAsModal = false } }}
And present it like:
struct ContentView: View { @State var presentingModal = false var body: some View { Button("Present") { self.presentingModal = true } .sheet(isPresented: $presentingModal) { ModalView(presentedAsModal: self.$presentingModal) } }}
Note that I passed the presentingModal
to the modal so you can dismiss it from the modal itself, but you can get rid of it.
To make it REALLY present fullscreen
(Not just visually)
You need to access to the ViewController
. So you need some helper containers and environment stuff:
struct ViewControllerHolder { weak var value: UIViewController?}struct ViewControllerKey: EnvironmentKey { static var defaultValue: ViewControllerHolder { return ViewControllerHolder(value: UIApplication.shared.windows.first?.rootViewController) }}extension EnvironmentValues { var viewController: UIViewController? { get { return self[ViewControllerKey.self].value } set { self[ViewControllerKey.self].value = newValue } }}
Then you should use implement this extension:
extension UIViewController { func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) { let toPresent = UIHostingController(rootView: AnyView(EmptyView())) toPresent.modalPresentationStyle = style toPresent.rootView = AnyView( builder() .environment(\.viewController, toPresent) ) NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "dismissModal"), object: nil, queue: nil) { [weak toPresent] _ in toPresent?.dismiss(animated: true, completion: nil) } self.present(toPresent, animated: true, completion: nil) }}
Finally
you can make it fullscreen
like:
struct ContentView: View { @Environment(\.viewController) private var viewControllerHolder: UIViewController? var body: some View { Button("Login") { self.viewControllerHolder?.present(style: .fullScreen) { Text("Main") // Or any other view you like// uncomment and add the below button for dismissing the modal // Button("Cancel") { // NotificationCenter.default.post(name: Notification.Name(rawValue: "dismissModal"), object: nil) // } } } }}
For iOS 14 and Xcode 12:
struct ContentView: View { @State private var isPresented = falsevar body: some View { Button("Show Modal with full screen") { self.isPresented.toggle() } .fullScreenCover(isPresented: $isPresented, content: FullScreenModalView.init) }}
struct FullScreenModalView: View { @Environment(\.presentationMode) var presentationModevar body: some View { VStack { Text("This is a modal view") } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.red) .edgesIgnoringSafeArea(.all) .onTapGesture { presentationMode.wrappedValue.dismiss() }}}
Hope this answer can help you all! Comment below about your result.
Ref: This Link
Disclaimer: Below is not really like a "native modal", neither behave nor look&feel, but if anyone would need a custom transition of one view over other, making active only top one, the following approach might be helpful.
So, if you expect something like the following
Here is a simple code for demo the approach (of corse animation & transition parameters can be changed by wish)
struct ModalView : View { @Binding var activeModal: Bool var body : some View { VStack { Button(action: { withAnimation(.easeInOut(duration: 0.3)) { self.activeModal = false } }) { Text("Hide modal") } Text("Modal View") } .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) .background(Color.green) }}struct MainView : View { @Binding var activeModal: Bool var body : some View { VStack { Button(action: { withAnimation(.easeInOut(duration: 0.3)) { self.activeModal = true } }) { Text("Show modal") } Text("Main View") } .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) .background(Color.yellow) }}struct ModalContainer: View { @State var showingModal = false var body: some View { ZStack { MainView(activeModal: $showingModal) .allowsHitTesting(!showingModal) if showingModal { ModalView(activeModal: $showingModal) .transition(.move(edge: .bottom)) .zIndex(1) } } }}