3

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?

1 Answers1

11

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

Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • How can I achieve this without pushing out the initial view. I want to FullScreen view to just slide in without any animation to ContentView – Zack Sep 29 '21 at 16:15
  • @Zack have you tried the first approach overlay with the writing animation keyword? – Raja Kishan Sep 29 '21 at 16:18
  • I was using that initially until I found a bug with Navigation bar on iOS 15. I have implemented the second solution successfully the only issue is I dont want to animate parent view, if possible – Zack Sep 29 '21 at 16:19
  • Okay @Zack by using second solution just change these setting. 1. update this line for presentcontroller function ```transition.type = CATransitionType.moveIn``` 2. update this line for dismissController function ```transition.type = CATransitionType.reveal``` – Raja Kishan Sep 29 '21 at 16:29
  • 1
    working perfect, and thanks for the quick response – Zack Sep 29 '21 at 16:31
  • solution 1 doesnt work in latest iOS, is there an updated version? – erotsppa Apr 08 '23 at 04:45