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

Custom navigation using setViewControllers(_:animated:) #79

Closed Ostroverkhov closed 2 years ago

Ostroverkhov commented 2 years ago

Есть UINavigationController, который содержит 5 контроллеров: 1->2->3->4->5. Я хочу добавить еще один после 2, т.е. должно получится так: 1->2->6.

В текущей реализации сначала происходит сброс до 2 контроллера, а потом переход на 6, что выглядит не очень красиво.

Подскажите, пожалуйста, как можно это решить?

ekazaev commented 2 years ago

@Ostroverkhov Добрый день

Обычно, это как раз поведение которого все хотят, так как оно более наглядно показывает пользователю что же происходит, вопрос красивости довольно субъективен :). Ну да бог с ним. Если, по какой то причине, вы хотите сделать свое поведение, то нужно посмотреть в сторону Action. Эта сущность отвечает за модификацию текущего графа вью контроллеров.

Возьмите за образец PushReplacingLastAction и напишите свою реализацию. Должно получиться что то вроде

struct PushAfterProductListAction<ViewController: UINavigationController>: ContainerAction {

    init() {}

   func perform(embedding viewController: UIViewController,
                        in childViewControllers: inout [UIViewController]) {
        if let index = childViewControllers.firstIndex(where: { $0 is ProductListViewController}) {
            childViewControllers = Array(childViewControllers.prefix(index))
        }
        childViewControllers.append(viewController)
    }

   func perform(with viewController: UIViewController,
                        on navigationController: ViewController,
                        animated: Bool,
                        completion: @escaping (_: RoutingResult) -> Void) {
        var viewControllers = navigationController.viewControllers
        perform(embedding: viewController, in: &viewControllers)
        navigationController.setViewControllers(viewControllers, animated: animated)
        if let transitionCoordinator = navigationController.transitionCoordinator, animated {
            transitionCoordinator.animate(alongsideTransition: nil) { _ in
                completion(.success)
            }
        } else {
            completion(.success)
        }
    }

}

let configuration =   StepAssembly(
            finder: ClassFinder<ProductDetailsViewController, Any?>(),
            factory: ClassFactory())
            .using(PushAfterProductListAction<UINavigationController>())
            .from(homeScreen.expectingContainer())
            .assemble()

(ЗЫ: Я не проверял компилируется ли код, так как не за компьютером, но, думаю, идея понятна)

Можете расширить Action что бы сделать его переиспользуемым и передавать способ найти нужный контроллер в конструкторе.

Пожалуйста, отпишитесь правильно ли я Вас понял и нужно ли что то еще.

Ostroverkhov commented 2 years ago

@ekazaev Спасибо за ответ) Да, Вы верно меня поняли)

Адаптировал код выше под себя, но что-то не удалось добиться нужного эффекта( В этой строчке я уже получаю нужный массив: var viewControllers = navigationController.viewControllers

И получается этот код не выполняет никакой работы:

if let index = childViewControllers.firstIndex(where: { $0 is ProductListViewController}) {
    childViewControllers = Array(childViewControllers.prefix(index))
}

И в итоге поведение не отличается от UINavigationController.push(). Пока не могу понять где у меня ошибка(

ekazaev commented 2 years ago

@Ostroverkhov Если вы уже получаете нужный массив значит он уже сформирован вашей конфигурацией. Обратите внимание на предыдущий шаг. Он уже выполнился к этому моменту и сфрмировал вам нужный массив. Вам нужно его изменить. Вам нужно что бы предыдуший шаг просто отдавал вам UINavigationController

Вместо .from(homeScreen.expectingContainer()) должно быть что то вроде допустим .from(GeneralStep.custom(using: ClassFinder<UINavigationController, Any?>())) или .from(GeneralStep.root().expectingContainer()) смотря что у вас там за структура приложения.

Если вы говорите .from(homeScreen.expectingContainer()) это значит что сперва нужно его найти (а вдруг его нет), показать, а потом на его UINavigationController применять Action. И к тому времени когда ваш Action начинает работать - там уже и так все хорошо, но Вам так не надо.

Ostroverkhov commented 2 years ago

@ekazaev Спасибо! Теперь все понятно

ekazaev commented 2 years ago

Hope you are satisfied with the result. Feel free to reopen the issue or create a new one if you’ll have another question.

Надеюсь, Вы дальше разберетесь, но если будут еще вопросы, или переоткройте эту задачу, или, если это не связано, создайте новую.