Search code examples
swiftswiftuiswiftui-navigationlink

SwiftUI: How to present a FullScreenCover Sideways


I'm making an app where multiple pages/views are embedded into a parent view that has a tab bar, menu bar, and header, Like this:

       HeaderView()
       MenuView()
       TabView(selection: $selected){
          HomeView()
          Page2View()
          Page3View()
          Page4View()
      }

When an user clicks on a list cell on one of the pages, I would like it to open into a detail view sideways, as opposed to opening as a sheet/modal that opens vertically, that hides the menu bar, header, and tabbar.. I've tried using a FullScreenCover for this, but it always presents bottom up, and I can't figure out how to present it from the side. I've also tried using a NavigationLink, but that keeps the tab bar, menu bar and header when I don't want them to show, and I can't figure out how to make the detail view fullscreen.

Do you know of any ways to either make a FullsSreenCover open sideways, or have a NavigationLink fill the entire screen when it is inside another view?


Solution

  • Use overlay with animation

    Parent view

    struct ContentView: View {
        @State var isShowSheet: Bool = false
        
        var body: some View {
            VStack{
                Button("Show Sheet") {
                    withAnimation {
                        isShowSheet.toggle()
                    }
                }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            .overlay(isShowSheet ? FullScreen(isShowSheet: $isShowSheet.animation()) : nil)
        }
    }
    

    FullScreen view

    struct FullScreen: View {
        
        @Binding var isShowSheet: Bool
        
        var body: some View {
            Color.blue
                .transition(.move(edge: .leading))
                .onTapGesture {
                    isShowSheet = false
                }
                .edgesIgnoringSafeArea(.all)
        }
    }
    

    enter image description here

    The second option is to use UIViewController with custom animation. I have use viewControllerHolder extesion from this link (// https://stackoverflow.com/a/58494173/14733292) and added CATransition()

    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 }
        }
    }
    
    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)
            )
            self.presentController(toPresent)
        }
        
        func presentController(_ viewControllerToPresent: UIViewController) {
            let transition = CATransition()
            transition.duration = 0.25
            transition.type = CATransitionType.push
            transition.subtype = CATransitionSubtype.fromLeft
            self.view.window?.layer.add(transition, forKey: kCATransition)
            
            present(viewControllerToPresent, animated: false)
        }
        
        func dismissController() {
            let transition = CATransition()
            transition.duration = 0.25
            transition.type = CATransitionType.push
            transition.subtype = CATransitionSubtype.fromRight
            self.view.window?.layer.add(transition, forKey: kCATransition)
            dismiss(animated: false)
        }
    }
    

    ContentView and FullScreen

    struct ContentView: View {
    
        @Environment(\.viewController) private var viewControllerHolder: UIViewController?
        
        var body: some View {
            VStack{
                Button("Show Sheet") {
                    viewControllerHolder?.present(style: .overFullScreen, builder: {
                        FullScreen()
                    })
                }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        }
    }
    
    struct FullScreen: View {
        
        @Environment(\.viewController) private var viewControllerHolder: UIViewController?
        
        var body: some View {
            Color.blue
                .onTapGesture {
                    viewControllerHolder?.dismissController()
                }
                .edgesIgnoringSafeArea(.all)
        }
    }
    

    enter image description here