frzi / swiftui-router

Path-based routing in SwiftUI
MIT License
919 stars 44 forks source link

Switch between flows #58

Closed IlyasNN closed 2 years ago

IlyasNN commented 2 years ago

Please provide information or example how to switch between different flows in case when one of the flow contains TabView.

So I have registration flow and main flow with tab bar (I use original swiftUI TabView with TabContents)

And I can't find approach how I can navigate to tabView after registration flow is completed. I get empty TabView. I suppose that's because tabView is a set of routes(views inside) and if I use tabView as root view it's okey. But if I use some empty root view as router that navigates to registration or tabView flow it can't navigate correctly to tabView

frzi commented 2 years ago

The quickest I can do now is link you to the TabView example which uses a TabView with SwiftUI Router. It uses a TabView at the top level of the view hierarchy, though the logic for a TabView inside a router shouldn't be too different. 🙂

IlyasNN commented 2 years ago

I have seen this example. TabView is used as a root view here... I want to create something like these:

IMG_5572 If I add TabView as second screen after empty root view for routing, I get empty tabView... Any ideas how to solve the issue?

frzi commented 2 years ago

Finally had a chance to give this a try. So far I experienced no problems getting the TabView to behave nicely.

Here's the entire contents for the ContentView. The only thing omitted here is the Router, which is wrapped around the ContentView in the App (inside the WindowGroup). This was tested on iOS 16.

import SwiftUI
import SwiftUIRouter

extension String: Identifiable {
    public var id: Self { self }
}

enum Constants {
    static let titles: [(title: String, image: String)] = [
        ("Movies", "film"),
        ("Music", "music.note"),
        ("Books", "book"),
    ]
}

struct ContentView: View {
    var body: some View {
        SwitchRoutes {
            Route("tabs/*") {
                TabsView()
            }

            Route {
                VStack {
                    ForEach(Array(Constants.titles.enumerated()), id: \.element.title) { (index, element) in
                        NavLink(to: "/tabs/" + element.title) {
                            Text("Go to /tabs/" + element.title)
                        }
                    }
                }
            }
        }
    }
}

private struct TabsView: View {
    @EnvironmentObject private var navigator: Navigator
    @EnvironmentObject private var routeInformation: RouteInformation
    @State private var selected = -1

    private func setTabForRoute(_ route: String) {
        if let index = Constants.titles.firstIndex(where: { $0.title == route }) {
            selected = index
        }
    }

    var body: some View {
        TabView(selection: $selected) {
            ForEach(Array(Constants.titles.enumerated()), id: \.element.title) { (index, element) in
                VStack {
                    Text(element.title)
                    NavLink(to: "/") {
                        Text("Back to root")
                    }
                }
                .tag(index)
                .tabItem {
                    Label(element.title, systemImage: element.image)
                }
            }
        }
        .onChange(of: selected) { newIndex in
            navigator.navigate(routeInformation.path + "/" + Constants.titles[newIndex].title)
        }
        .onChange(of: navigator.path) { newPath in
            let components = newPath.components(separatedBy: "/").dropFirst()
            if let route = components.first {
                setTabForRoute(route)
            }
        }
        .onAppear {
            let components = navigator.path[routeInformation.path.endIndex...].components(separatedBy: "/").dropFirst()
            if let route = components.first {
                setTabForRoute(route)
            }
        }
    }
}

The code was written hasty. Plenty of room for cleaning up 🙂