frzi / swiftui-router

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

Can't revisit a page #44

Closed zpg6 closed 2 years ago

zpg6 commented 2 years ago

A much needed package, thank you for bringing this to the community!

Sadly, I ran into an issue on my first run testing things out. It's probably not the most common series of events but here's my setup:

Router {
  SwitchRoutes {
    Route("start") {
      StartView()
    }
    Route {
      NavLink(to: "start") {
        Text("Go to start")
      }
    }
  }
}
  1. When app opens, it goes to the catch-all route (as expected).

  2. Pressing NavLink takes me to StartView (as expected).

  3. StartView uses Navigator EO and a button with one line: navigate(to: "overview"). Tapping that button opens the catch-all route (as expected).

  4. When tapping NavLink again I expected it to go to the StartView again. Sometimes it navigates to a blank page and sometimes nothing occurs at all.

Maybe this is the outside the intended functionality, if so I wonder if the documentation could better reflect that.

EDIT: Initially the only result on Step 4 was navigating to a blank screen, so I thought this might be due to how I'm handling my onAppear/onDisappear in StartView. But now it's just staying out and not navigating at all.

I even tried a slightly different example...

Router {
  SwitchRoutes {
    Route("start") {
      StartView()
    }
    Route("overview") {
      NavLink(to: "info") {
        Text("Go to info")
      }
    }
    Route {
      NavLink(to: "start") {
        Text("Go to start")
      }
    }
  }
}

Now it gets to the catch all from "overview", but again nothing happens when tapping NavLink to go to "start".

frzi commented 2 years ago

Thanks for trying out SwiftUI Router!

I tested your code and could replicate the behaviour. But all seems to be working as intended. 😄

Two key things to remember with SwiftUI Router: 1) Paths are always relative. When you call navigate(to: "overview") (in StartView) it adds overview to the current path. In this case the current path is /start. And so the navigator will navigate to /start/overview. Prefix the path with a / to use absolute paths (thus: navigate(to: "/overview")). This copies the same behaviour as links do on the web.
I see this information is indeed lacking in the readme, but it's mentioned multiple times in the code's documentation (a, b, c). (And Xcode has an exquisite documentation viewer 😄)

2) Paths need to end with a /* to also match deeper paths. E.g.: a Route with the path overview would not match the path /overview/start. The path should be defined as overview/*. This is why, after the first time pressing the "Go to start" button nothing happens, as there is no Route with a path that matches /overview/start.

One tip to keep track of the current path is to use an onChange view modifier somewhere (preferably high up in your view hierarchy) and keeping track of the Navigator's path:

@main struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            Router {
                RootView()
            }
        }
    }
}

struct RootView: View {
    @EnvironmentObject var navigator: Navigator

    var body: some View {
        SomeOtherViewHandlingTheRoutes()
            .onChange(of: navigator.path) { newPath in
                print("new path:", newPath)
            }
    }
}

If there are any other questions please let me know! 🙇

zpg6 commented 2 years ago

Ah, this makes total sense. Getting the behavior I want now that I've made the corresponding changes.

Thanks for quick reply and detailed feedback!