Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation bar #2

Open
guyschlider opened this issue Jan 23, 2020 · 9 comments
Open

Navigation bar #2

guyschlider opened this issue Jan 23, 2020 · 9 comments
Labels
enhancement New feature or request

Comments

@guyschlider
Copy link

Hi!

This is some impressive work! Thanks for that!

We are really facing lots of issues with the NavigationView comes with SwiftUI and looking for an alternative, the problem is that we do need the option to add a "Back" button to views, similar to the way native NavigationView let you.

Is there any example of how to do so? or maybe while using another library that provides a drop-in bar?

Thanks again!

@matteopuc
Copy link
Owner

Hi @mp3il, thank you. NavigationStackView is really new and it doesn't support any navigation bar yet. To be honest it shouldn't be that hard to develop an integrated navigation bar for it, but I really haven't had time to try. Meanwhile, there's something you can do exploiting the NavigationStackView and adding a couple of generic views to your project:

Let's create a generic building block called AppScreen (i.e. a generic screen for your app that can show back buttons)

struct AppScreen<Content>: View where Content: View {
    private let showBackButton: Bool
    private let content: Content

    init(showBackButton: Bool = false, content: () -> Content) {
        self.showBackButton = showBackButton
        self.content = content()
    }

    var body: some View {
        ZStack {
            Color.myAppBgColour.edgesIgnoringSafeArea(.all)
            content
            if showBackButton {
                BackButton()
            }

        }
    }

    private struct BackButton: View {
        var body: some View {
            VStack {
                HStack {
                    PopView {
                        Text("< Back")
                            .modifier(NavigationLinkStyle())
                    }
                    Spacer()
                }
                Spacer()
            }
        }
    }
}

struct NavigationLinkStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .foregroundColor(.blue)
    }
}

extension Color {
    static let myAppBgColour = Color.white
}

Basically every screen of your app has to be an AppScreen view. This can be your root view:

struct AppRootView: View {
    var body: some View {
        NavigationStackView {
            AppScreen {
                VStack {
                    Text("ROOT VIEW")
                    PushView(destination: SecondView()) {
                        Text("PUSH FORWARD")
                            .modifier(NavigationLinkStyle())
                    }
                }
            }
        }
    }
}

Note that AppRootView must contain the NavigationStackView. Then, just as an example, we can define a couple of screens to trigger the navigation:

struct SecondView: View {
    var body: some View {
        AppScreen(showBackButton: true) {
            VStack {
                Text("SECOND VIEW")
                PushView(destination: ThirdView()) {
                    Text("PUSH FORWARD")
                        .modifier(NavigationLinkStyle())
                }
            }
        }
    }
}

struct ThirdView: View {
    var body: some View {
        AppScreen(showBackButton: true) {
            Text("THIRD VIEW")
        }
    }
}

This is the complete example:

import SwiftUI
import NavigationStack

struct NavigationLinkStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .foregroundColor(.blue)
    }
}

extension Color {
    static let myAppBgColour = Color.white
}

struct AppScreen<Content>: View where Content: View {
    private let showBackButton: Bool
    private let content: Content

    init(showBackButton: Bool = false, content: () -> Content) {
        self.showBackButton = showBackButton
        self.content = content()
    }

    var body: some View {
        ZStack {
            Color.myAppBgColour.edgesIgnoringSafeArea(.all)
            content
            if showBackButton {
                BackButton()
            }

        }
    }

    private struct BackButton: View {
        var body: some View {
            VStack {
                HStack {
                    PopView {
                        Text("< Back")
                            .modifier(NavigationLinkStyle())
                    }
                    Spacer()
                }
                Spacer()
            }
        }
    }
}

struct AppRootView: View {
    var body: some View {
        NavigationStackView {
            AppScreen {
                VStack {
                    Text("ROOT VIEW")
                    PushView(destination: SecondView()) {
                        Text("PUSH FORWARD")
                            .modifier(NavigationLinkStyle())
                    }
                }
            }
        }
    }
}

struct SecondView: View {
    var body: some View {
        AppScreen(showBackButton: true) {
            VStack {
                Text("SECOND VIEW")
                PushView(destination: ThirdView()) {
                    Text("PUSH FORWARD")
                        .modifier(NavigationLinkStyle())
                }
            }
        }
    }
}

struct ThirdView: View {
    var body: some View {
        AppScreen(showBackButton: true) {
            Text("THIRD VIEW")
        }
    }
}

struct BackButtonPreview_Previews: PreviewProvider {
    static var previews: some View {
        AppRootView()
    }
}

The result is:

Jan-23-2020 18-01-42

Of course, you can customise the back button part as you wish, I just created a very simple example.

@guyschlider
Copy link
Author

Wow, Thanks so much! I learn so much from this project and also this example.

btw, What I've ended done doing was a few modifications to your project so I can access the nav view model from the parent view, so I can push views from the parent and not just from the child views (that have access to the environment object)

It's a bit hacky, though, but it allows me to push views not from within subviews of the NavigationStack

So I do use the NavigationView, with only a container view that has a NavigationStack, which is exposing its navViewModel.

Though I wasn't able to think about a cleaner way to expose a Singleton of the NavigationStack, maybe it will require creating the ViewModel and injecting it to the actual view?

Thanks again!

@matteopuc
Copy link
Owner

If you want you can point me to your fork and I'll take a look to see if I can give you some more specific hints. Anyway: if you turned the NavigationStack into an @EnvironmentObject you need to inject the navigation stack itself in the NavigationStackView from the outside. For example directly from the SceneDelegate:

// Create the SwiftUI view that provides the window contents.
let contentView = RootView().environmentObject(NavigationStack())

This way you will have a navigation stack accessible from everywhere (from the inside and from the outside of the NavigationStackView).

@gannonprudhomme
Copy link

I'd also be interested in using this with a navigation bar. Was about to completely switch to using this in my project until finding out it didn't support nav bars out-of-the-box.

Regardless, thanks a bunch for project!

@matteopuc matteopuc changed the title [Question] Using with a navigation bar / alternative [Improvement] Navigation bar Apr 15, 2020
@mroushdy
Copy link

+1 for navigation. Forward and backward would be awesome. Gestures would be ++

@matteopuc matteopuc added the enhancement New feature or request label Sep 25, 2020
@matteopuc matteopuc changed the title [Improvement] Navigation bar Navigation bar Sep 25, 2020
@Kitos-dev
Copy link

Hi, I've been having a lot of issues with navigation in SwiftUI, basically when used Published vars trying to control navigation 🙄 When pushing a second view the binder var for previous pushed one was set to false by system ... it must babe something with navigation views.
Anyway, researching about this, I landed here and I'm trying to use it right now.
An awesome work btw.
The case is that I need Navigation bar as it is usual so I vote for this.
Although it is not so difficult to add something as your example @matteopuc, it would be awesome to include this functionality in this framework.

@gbrooker
Copy link

I've been able to combine NavigationStackView with NavigationView to use a native navigation bar. See the example in the readme in PR #44

@dbarchmann
Copy link

I've been able to combine NavigationStackView with NavigationView to use a native navigation bar. See the example in the readme in PR #44

I can't find an example on how to use the navigation bar in the readme. Tried the old NavigationView way, but doesn't seem to work.
Do I understand correctly that it is possible to use a native navigation bar? Or is just a Back link like in the example above. Any help would be highly appreciated. Thanks for the great work so far.

Cheers

@ecteodoro
Copy link

Unfortunately it's useless without support to Navigation Bar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

8 participants