alexdremov / PathPresenter

Pure SwiftUI state-driven library to present view sequences and hierarchies.
72 stars 4 forks source link

[ Question ] How to use path variable in child view #1

Closed iRiziya closed 2 years ago

iRiziya commented 2 years ago

I am following the given example but it is still unclear that how can I use the same path variable in child view. The main path variable should be accessible to all its child view and must be binded.

I have LoginView and ForgetPasswordView. I have declared path as below in LoginView

@State var path = PathPresenter.Path()

and embedded login layout inside PathPresenter like this

PathPresenter.RoutingView(path: $path){ 
     VStack{
          //Login layout
     }
 }

and then I am appending ForgetPasswordView in this path

path.append(ForgetPasswordView(),
                       type: .animated(transition: .move(edge: .leading), animation: .easeIn))

but now the back button is inside the ForgetPasswordView so I am wondering how can I use the same path variable to go back or to append any new View?

alexdremov commented 2 years ago

Hello!

You can pass a path binding to ForgetPasswordView by defining @Binding var path: PathPresenter.Path in ForgetPasswordView.

Then, you can pass path during initialisation of ForgetPasswordView:


path.append(ForgetPasswordView(path: $path),
                       type: .animated(transition: .move(edge: .leading), animation: .easeIn))

Now you can use path in ForgetPasswordView as usual

alexdremov commented 2 years ago

Also, architecture-wise, I would suggest not to pass path all around the app.

It may be fine in small examples like yours, but on a large scale, if views will push and pop uncontrollably, it will be a mess

iRiziya commented 2 years ago

Thanks for the quick response. @AlexRoar

I already tried @Binding but its not working as expected. And also my app is large so what would be best approach in that case?

iRiziya commented 2 years ago

Alright @Binding worked this time because I removed the embedded PathPresenter from ForgetPasswordView. But I am worrying about infinite navigation chain If I will have to navigate through so many screens.

alexdremov commented 2 years ago

I already tried @Binding but its not working as expected. And also my app is large so what would be best approach in that case?

Complete working example:

import SwiftUI
import PathPresenter

struct ContentView: View {
  @State var path = PathPresenter.Path()
  var body: some View {
    PathPresenter.RoutingView(path: $path){
       VStack {
         Spacer()
         Button("Forgot password") {
           path.append(
            ForgotPasswordView(path: $path),
            type: .animated(
              transition: .scale,
              animation: .easeIn)
           )
         }
         Spacer()
       }
       .frame(maxWidth: .infinity, maxHeight: .infinity)
     }
    .border(.blue)
  }
}

struct ForgotPasswordView: View {
  @Binding var path: PathPresenter.Path

  var body: some View {
    VStack(alignment: .center){
      Spacer()
      VStack {
        Text("Restore password screen")
        Button("back") {
          path.removeLast()
        }
      }
      .background(.white)
      Spacer()
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
  }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
alexdremov commented 2 years ago

Alright @Binding worked this time because I removed the embedded PathPresenter from ForgetPasswordView. But I am worrying about infinite navigation chain If I will have to navigate through so many screens.

You need to somehow restrict subviews to be able to do limited actions with path. In your example you do not need to pass the whole path to the subview. You can pass some closure like backAction: () -> () that will be called when user presses back button.

And inside this closure you will do needed actions with path.

Also, check out MVVM-C architecture. I believe it's a nice extension of MVVM that has good control over navigation.

alexdremov commented 2 years ago

Also, I'm planning to extend this library with wrapper around path that will restrict views from doing some mess with all other views ;)

iRiziya commented 2 years ago

Alright got it. Thanks.

iRiziya commented 2 years ago

Can you make it global? so we can use it on every page as an EnvironmentObject.

I am using a similar library for SwiftUI navigation but its not working for macOS because of missing NavigationView

alexdremov commented 2 years ago

Can you make it global? so we can use it on every page as an EnvironmentObject.

You can make a class wrapper and pass it as EnvironmentObject

class PathObserved: ObservableObject {
    @Published var path = PathPresenter.Path()
}

But if you need this object in global space, it's a smell of bad code architecture. This library was specifically designed without EnvironmentObjects and ObservableObject.

iRiziya commented 2 years ago

Ah ok. It's hard to deal with Navigation in macOS so I'll have to go for any of this. Can the given class wrapper create a mess for large app having many pages?

alexdremov commented 2 years ago

Can the given class wrapper create a mess for large app having many pages?

The biggest concern I have is such situation:

Result: view B removed unexpectedly, and view A is not not removed. So, yes. Uncontrollable pushing/popping of views will hurt your app.

I will try to address such issue in future releases, creating new structure for managing such situations.

iRiziya commented 2 years ago

Ok thanks