5

我正在使用模式MVVM+Coordinator。我的每个控制器都是由coordinators. Navigation但是当点击控制器的后退按钮时停止我的协调员的正确方法是什么?

class InStoreMainCoordinator: NavigationCoordinatorType, HasDisposeBag {

    let container: Container

    enum InStoreMainChildCoordinator: String {
        case menu = "Menu"
        case locations = "Locations"
    }

    var navigationController: UINavigationController
    var childCoordinators = [String: CoordinatorType]()

    init(navigationController: UINavigationController, container: Container) {
        self.navigationController = navigationController
        self.container = container
    }

    func start() {
        let inStoreMainViewModel = InStoreMainViewModel()
        let inStoreMainController = InStoreMainController()
        inStoreMainController.viewModel = inStoreMainViewModel

        navigationController.pushViewController(inStoreMainController, animated: true)
    }
}
4

5 回答 5

2

在阅读了许多关于协调器的文章并看到了一些复杂的想法(如路由器、一些令人费解的魔法和自定义导航控制器委托)之后,我现在要做的是:

View Controller 强烈拥有 Coordinator,如果有的话,Coordinator 对 View Controller 的引用很弱。Coordinator 对其父级的引用很弱,以支持 Coordinator 对象之间通信的责任链。

(责任链设计模式的示例是 iOS 中的响应者链。)

当您在某个协调器上调用 stop 时,视图控制器会从堆栈中弹出,释放并释放协调器。因此,当点击后退按钮并关闭视图控制器时,协调器将被释放。

这对我有用,因为不需要构建额外的基础设施。

最初,我通过构建符合 UINavigationControllerDelegate 协议的 NavigationControllerMutliDelegate 类解决了 UINavigationControllerDelegate 问题。它具有注册/注销逻辑。然后这个对象被传递给每个协调器,以便在视图控制器关闭时通知协调器。NavigationControllerMutliDelegate 是访问者设计模式的一个示例,它有一堆协调器,并且在 View Controller 出现/关闭时,它通过向每个协调器发送一个对象来通知所有协调器。

但是,最后,当看到有多少代码和不必要的复杂性时,我只是选择了拥有 Coordinator 的 View Controller。我只希望对象位于 View Controller 之上,它保留数据提供者、服务、视图模型等,以便 View Controller 更干净。我不想重新发明协调器的推送弹出堆栈并处理这么多所有者问题。就像我想要一些东西来缓解我的生活而不是让它变得更加复杂......

于 2020-11-03T14:36:31.363 回答
1

协调器模式有一个关于原生后退按钮的已知盲点。您主要有两种方法可以解决它:

  • 重新实现您自己的后退按钮,尽管您松开了本机向后滑动手势来导航。
  • 实现UINavigationControllerDelegate以检测视图何时弹出以便能够释放匹配的协调器。

关于第一个解决方案,我不建议这样做,用户会为您的代码架构付出代价,这听起来不公平。

对于第二个,您可以按照@mosbah 的建议将其实现到协调器本身,但我建议您更进一步,使用NavigationControllerorRouter类将导航与协调器分开,以隔离导航本身并保持清晰的关注点分离.

我在这里写了一些关于它的东西,详细说明了主要步骤。

于 2019-04-05T03:16:41.940 回答
1

我的方法是使用管理子协调器的根(父)协调器,因此当用户完成流程或点击后退按钮时,会调用根协调器中的委托方法,它可以清理子协调器并在需要时创建一个新的.

于 2019-01-12T07:18:06.347 回答
1

我的解决方案是使用一个函数作为我的协调器而不是一个类。这样我就完全没有所有权问题。当点击后退按钮时,来自视图控制器的视图会发出完成的事件,一切都会自然地展开,而我不费吹灰之力。

您在示例中显示的start()内容可以通过以下方式更简单地表达:

func startInStore(navigationController: UINavigationController) {
    let inStoreMainViewModel = InStoreMainViewModel()
    let inStoreMainController = InStoreMainController()
    inStoreMainController.viewModel = inStoreMainViewModel

    navigationController.pushViewController(inStoreMainController, animated: true)
}

可以在此处找到使用此样式的示例应用程序:https ://github.com/danielt1263/RxMyCoordinator

于 2020-11-04T13:18:08.633 回答
0

代替使用子协调器,您可以编写您的协调器类,使它们根本不需要保留。实际上,在您给出的示例中,没有什么需要保留此类,您甚至可以将其最小化为以下形式:

class InStoreMainCoordinator {
    func start(with navigationController: UINavigationController, container: Container) {
        let inStoreMainViewModel = InStoreMainViewModel()
        let inStoreMainController = InStoreMainController()
        inStoreMainController.viewModel = inStoreMainViewModel
        navigationController.pushViewController(inStoreMainController, animated: true)
    }
}

然后InStoreMainCoordinator().start(with: navigationController, container: container)在您想要启动此屏幕时调用。您根本不需要保留对此 InStoreMainCoordinator 的强引用。这样您就不会对后退按钮有任何问题,因为您不需要解除分配这些协调器。它们仅在您将屏幕切换到新屏幕时才存在。为了更好地理解此方法,假设您有另一个屏幕,例如 InStoreDetailsController 类,并且在单击 InStoreMainController 上的某些内容后应该启动详细信息屏幕。然后你可以实现两个与这些视图控制器相关的协调器类,如下所示:

class InStoreMainCoordinator {
    func start(with navigationController: UINavigationController, container: Container) {
        let inStoreMainViewModel = InStoreMainViewModel(onStoreSelected: { storeId in
            InStoreDetailsCoordinator().start(with: navigationController, container: container, dependencies: .init(storeId: storeId))
        })
        let inStoreMainController = InStoreMainController()
        inStoreMainController.viewModel = inStoreMainViewModel
        navigationController.pushViewController(inStoreMainController, animated: true)
    }
}
class InStoreDetailsCoordinator {
    struct Dependencies {
        var storeId: String
    }
    func start(with navigationController: UINavigationController, container: Container, dependencies: Dependencies) {
        let inStoreDetailsViewModel = InStoreDetailsViewModel(storeId: dependencies.storeId)
        let inStoreDetailsController = InStoreDetailsController()
        inStoreDetailsController.viewModel = inStoreDetailsViewModel
        navigationController.pushViewController(inStoreDetailsController, animated: true)
    }
}

正如您所看到的,如果您使用闭包而不是委托模式,您可以在单个函数中编写与一个屏幕相关的所有内容(包括将其推送到屏幕上并处理与例如从该屏幕移动到另一个屏幕相关的事件)。这样,您在需要切换屏幕时调用的协调器中的每个屏幕只有一个方法,并且您不需要保留它们,因为需要保留的所有内容都由其他东西保留(在上面的示例中,视图模型保留onStoreSelected 参数中给出的处理程序,用于切换到另一个屏幕)。我认为这个解决方案比使用儿童协调员更简单。它工作正常,不需要对后退按钮进行任何额外的特殊处理。

另一种可以很好地工作的替代解决方案,特别是如果您的应用程序中没有大量的屏幕,是为您的主AppCoordinator类或您命名的任何视图控制器中的每个视图控制器创建startNameOfYourScreen(...)方法。正如您在上面看到的,如果您使用闭包而不是委托模式,您可以在一个函数中编写与一个屏幕相关的所有内容,这样可以保持非常简单。您可以选择将这些函数拆分为AppCoordinator类的扩展,并将它们放入单独的文件中,以便在您的项目中有更好的组织。在此解决方案中,后退按钮也没有任何问题,因为您根本不需要实例化子协调器,也不需要解除分配它们。

但是,如果由于某种原因您决定仍要使用子协调员的方式,那么这里有一些关于在使用子协调员时可能解决后退按钮问题的文章的链接供参考:

于 2021-07-02T17:17:42.630 回答