Search code examples
swiftuipresentmodalviewcontrollerswiftui-navigationlinkswiftui-navigationview

How to Push View or Present View Modally in SwiftUI dynamically?


I'm learning SwiftUI and my focus at the moment is to implement a method which I'm able to implement using UIKit. I need to create a method the determines whether I should Push the View or Present modally the view based on the value of a boolean.

In UIKit, my code is:

var presentVC = true // boolean that determines whether VC will be presented or pushed

let vc = ViewController() //Your VC that will be pushed or presented

if (presentVC == true) {
     self.presentViewController(vc, animated: true, completion: nil)
} else {
    self.navigationController.pushViewController(vc, animated: true)
}

But in SwiftUI, I'm not sure how to implement this correctly using the:

  • NavigationLink - For pushing the View
  • .sheet(isPresented:, content:) - For presenting the View modally

It seems that the NavigationLink and .sheet modifier is coupled with the view implementation. Does anyone already encountered and solve this scenario in SwiftUI? Thanks

I'm using SwiftUI 1.0 as I need to support iOS 13.


Solution

  • A possible solution is to create a custom enum with available presentation types:

    enum PresentationType {
        case push, sheet // ...
    }
    

    and create a custom binding for activating different views:

    func showChildView(presentationType: PresentationType) -> Binding<Bool> {
        .init(
            get: { self.showChildView && self.presentationType == presentationType },
            set: { self.showChildView = $0 }
        )
    }
    

    Full code:

    struct ContentView: View {
        @State var presentationType = PresentationType.push
        @State var showChildView = false
    
        func showChildView(as presentationType: PresentationType) -> Binding<Bool> {
            .init(
                get: { self.showChildView && self.presentationType == presentationType },
                set: { self.showChildView = $0 }
            )
        }
    
        var body: some View {
            NavigationView {
                VStack {
                    Button(action: {
                        self.presentationType = .push
                        self.showChildView = true
                    }) {
                        Text("Present new view as Push")
                    }
                    Button(action: {
                        self.presentationType = .sheet
                        self.showChildView = true
                    }) {
                        Text("Present new view as Sheet")
                    }
                }
                .navigationBarTitle("Main view", displayMode: .inline)
                .background(
                    NavigationLink(
                        destination: ChildView(),
                        isActive: self.showChildView(presentationType: .push),
                        label: {}
                    )
                )
            }
            .sheet(isPresented: self.showChildView(presentationType: .sheet)) {
                ChildView()
            }
        }
    }
    
    struct ChildView: View {
        var body: some View {
            ZStack {
                Color.red
                Text("Child view")
            }
        }
    }