rundfunk47 / stinsen

Coordinators in SwiftUI. Simple, powerful and elegant.
MIT License
907 stars 95 forks source link

Present a view on top of a NavigationView #21

Closed dlvovich closed 3 years ago

dlvovich commented 3 years ago

Hi, I need to present a full-screen cover view. It must be half-transparent and with iOS 13 support, so I can't use fullScreen transition.

So I need to get NavigationView and wrap it into ZStack. There is the problem. In Stinsen NavigationView created deep inside the lib, in MyNavigationViewCoordinatorView and I can't get it.

I solved it by rewriting MyNavigationViewCoordinatorView and removing the creation of NavigationView in it. Then I create NavigationView manually and wrap it in ContainerView.

Something with that:

class MainCoordinator: ViewCoordinatable {
    var children = ViewChild()

    enum Route: ViewRoute {
        case wallList
    }

    func resolveRoute(route: Route) -> AnyCoordinatable {
        switch route {
        case .wallList:
            let coord = WallsListCoordinator()
            return AnyCoordinatable(
                MyNavigationViewCoordinator(coord, customize: { view in
                    ContainerView {
                        view
                    }
                })
            )
        }
    }

    @ViewBuilder func start() -> some View {
        Resolver.resolve(StartView.self)
    }
}

struct ContainerView<Content: View>: View {
    @ViewBuilder var content: Content

    @StateObject var popup = Popup()

    var body: some View {
        ZStack {
            NavigationView {
                content
            }
            .environmentObject(popup)

            if self.popup.show {
                Color.black.opacity(0.8)
                    .edgesIgnoringSafeArea(.all)
            }
        }
    }
}

It works, but it's not a very good approach really. Is there any better way to do it?

I need to inform NavigationViewCoordinator that It shouldn't create NavigationView because I created and send it in customize variable.

rundfunk47 commented 3 years ago

Hi! Every coordinator has a non-documented function called "customize" that can be implemented if you intend to customize the views in the coordinator. Use the customize function like this:

class MainCoordinator: ViewCoordinatable {
    var children = ViewChild()

    enum Route: ViewRoute {
        case wallList
    }

    func resolveRoute(route: Route) -> AnyCoordinatable {
        switch route {
        case .wallList:
            return AnyCoordinatable(
                NavigationViewCoordinator(WallsListCoordinator())
            )
        }
    }

    @ViewBuilder public func customize(_ view: AnyView) -> some View {
        PopupWrapper(content: view)
    }

[...]

struct PopupWrapper: View {
    @StateObject var popup = Popup()
    var content: AnyView

    var body: some View {
        ZStack {
            view.environmentObject(popup)
            if self.popup.show {
                Color.black.opacity(0.8)
                    .edgesIgnoringSafeArea(.all)
            }
        }
    }
}
dlvovich commented 3 years ago

@rundfunk47

Hi!

I can't do it like this. Because you create NavigationView in NavigationViewCoordinatorView.swift and wrap customize inside it. Something like that:

#else
        NavigationView {
            customize(AnyView(view))
        }
        .navigationViewStyle(StackNavigationViewStyle())
        .onReceive(coordinator.children.$childCoordinator) { (value) in
        ...

But I need to create a NavigationView myself, set environmentObject for it and send it in customize func. Otherwise my PopupWrapper won't be on the top of NavigationView as I need it to.

rundfunk47 commented 3 years ago

Hi! There's a version 2.0.0 now that has a ViewWrapperCoordinator that you could use. Please try it out!

dlvovich commented 3 years ago

Hi! Our app is in production now. I'll try the new feature when I work on the next release.

Thanks