ekazaev / route-composer

Protocol oriented, Cocoa UI abstractions based library that helps to handle view controllers composition, navigation and deep linking tasks in the iOS application. Can be used as the universal replacement for the Coordinator pattern.
MIT License
896 stars 63 forks source link

Help finding a contained navcontroller within UISplitView #28

Closed benconstantine39 closed 5 years ago

benconstantine39 commented 5 years ago

I can't figure out how to push a detail view onto a split view's current detail view. The split view's detail view hierarchy is a navigation controller with a view, but I can't get the router to find the navigation controller (highlighted in the attached screenshot):

var detailOne: DestinationStep<DetailOneController, DetailContext> {
    return StepAssembly(
      finder: ClassFinder<DetailOneController, DetailContext>(),
      factory: StoryboardFactory(storyboardName: "People", viewControllerID: "DetailOneController"))
      .using(UISplitViewController.pushToDetails())
      .from(peopleSplitView.expectingContainer())
      .assemble()
  }

  var detailTwo: DestinationStep<DetailTwoController, DetailContext> {
    return StepAssembly(
      finder: ClassFinder<DetailTwoController, DetailContext>(),
      factory: DetailTwoControllerFactory(dependencies: personDependencies))
      // .using(UINavigationController.push())
      // .from(detailOne.expectingContainer())
      .using(UISplitViewController.pushToDetails())
      .from(peopleSplitView.expectingContainer())
      .assemble()
  }

In the detailTwo assembly, if I use the commented out lines UINavigationController.push from detailOne.expectingContainer, the router can't find the split view's second contained navigationController and says "Type Mismatch Error: Container of UINavigationController type cannot be found to perform PushAction()", and if I instead use UISplitViewController.pushToDetails from peopleSplitView.expectingContainer it replaces the entire detail hierarchy (nav controller and DetailOneController) with a DetailTwoController and then the user can't navigate back to detailOne.

This is only causing me trouble in landscape orientation with a regular horizontal size class (in portrait it pushes on detailTwo just fine).

heirarchy
benconstantine39 commented 5 years ago

I got it to work finally by creating a custom container action similar to UISplitViewController.PushToDetailsAction and plucking out the nav controller I wanted to push on to within perform(embedding: in:). Not sure it's the best way, but works for now 👍🏻

ekazaev commented 5 years ago

@benconstantine39 This is how it should be done at the end. I remember i had to do the same manually myself couple of years ago. UISplitViewConnroller breaks a lot of rules of UIKit, so it need some custom approaches depending on the layout and the custom behaviour. It could've been done with the custom action or using the custom finder that will find second UINavigationController in the hierarchy. It is impossible to insert UINavigationControllerin to another UINavigationController using UIKit, only UISplitViewConnroller. You may need more custom actions in future, especially if you are changing UISplitViewConnroller default behaviour using its delegate. Also, there will be new version released soon, that will allow to change default behaviour of the ContainerViewController. It is impossible to do currently. It may also help you with some issues. Let me know if i can help you with something like this

ekazaev commented 5 years ago

It would be nice if you can contribute your action in to the library

ekazaev commented 5 years ago

@benconstantine39 Also, i am not aware if you know about this option. You can combine default finders results to be able to make a complicated search. For example a finder that will find second UINavigationController may look like this:

ClassFinder<UINavigationController, Any?>(options: .contained, startingPoint: .custom(ClassFinder<UINavigationController, Any?>().findViewController())

or this:

let secondNavigationController = ClassFinder<UINavigationController, Any?>(options: .contained, startingPoint: .custom(ClassFinder<UINavigationController, Any?>().findViewController()).findViewController()

Of course, in case of UISplitViewController you will need to search in the 2 different ways depending if it is collapsed or not.

But i decided to remind about this option just in case. It stays hidden for some people.

benconstantine39 commented 5 years ago

Sure, I'd be happy to contribute the action I wrote. Thanks for your quick feedback!