johnpatrickmorgan / FlowStacks

FlowStacks allows you to hoist SwiftUI navigation and presentation state into a Coordinator
MIT License
832 stars 65 forks source link

Combining Push with Present #6

Closed rebeloper closed 2 years ago

rebeloper commented 2 years ago

I did not find a way to have both a push and present navigation type on the same view. Is this possible? Thank you

johnpatrickmorgan commented 2 years ago

Thanks, it's a great question.

As you've probably found, having a PStack that contains one or more NStacks within a single view can make state management tricky, as there could be any number of navigation flows to manage. It's easier to break the state out into multiple coordinator views, e.g. a parent coordinator view managing a presentation stack containing child coordinators / views, each of which might themselves manage a navigation stack. If you're using view models, you could have separate view models for the presentation and navigation layers, but still have a single coordinator view. Or, if you have an NStack of which there is always exactly one at the root of your PStack, you could handle both within a single coordinator (though it would behave strangely if you ever presented more than one of your root navigation stacks), e.g.:

struct PNCoordinator: View {

  enum Screen {
    case root
    case hi
  }

  enum RootScreen {
    case home
    case hello
  }

  @State var rootNFlow = NFlow<RootScreen>(root: .home)
  @State var mainPFlow = PFlow<Screen>(root: .root)

  var body: some View {
    PStack($mainPFlow) { screen in
      switch screen {
      case .root:
        NavigationView {
          NStack($rootNFlow) { screen in
            switch screen {
            case .home:
              VStack(spacing: 8) {
                Text("Home")
                Button("Push Hello", action: pushHello)
                Button("Present Hi", action: presentHi)
              }
            case .hello:
              VStack(spacing: 8) {
                Text("Hello")
                Button("Pop", action: pop)
                Button("Present Hi", action: presentHi)
              }
            }
          }
        }
      case .hi:
        VStack(spacing: 8) {
          Text("Hi")
          Button("Dismiss", action: dismiss)
        }
      }
    }
  }

  func pushHello() {
    rootNFlow.push(.hello)
  }

  func presentHi() {
    mainPFlow.present(.root)
  }

  func pop() {
    rootNFlow.pop()
  }

  func dismiss() {
    mainPFlow.dismiss()
  }
}

In any case, I'd like to explore ways of making it easier to combine both within a single coordinator view, such as a stack that can be used for both pushing and presenting views.

rebeloper commented 2 years ago

Thanks for the reply. Had not time to wait for your answer :) so I created my own coordinator that can use both push and present actions: https://github.com/rebeloper/Dot/tree/main/Sources/Dot/LinkedNavigation Let me know what you think. It's heavily influenced by this repo. Thank you.

johnpatrickmorgan commented 2 years ago

Nice one! In fact, this issue inspired me to try it out too, and I think my solution is very similar to yours. Allowing the option to embed within a NavigationView when presenting is the critical part I think. Here's the branch where I've been working on it, though it's not quite shippable yet.

I'll take a closer look at your repo and how you're using it, thanks for sharing!

johnpatrickmorgan commented 2 years ago

Support for combined navigation and presentation has now been added in v0.1.0 using the new Router. 🎉