ADCoordinator 1.1.0

ADCoordinator 1.1.0

CI FabernovelPierre FelginesClaire Peyron 维护。



ADCoordinator

CI Version License Platform

示例

要运行示例项目,请先克隆仓库,然后在 Example 目录中首先运行 pod install

要求

ADCoordinator 使用 Swift 5.1 编写,兼容 iOS 10.0 以上。

安装

ADCoordinator 通过 CocoaPods 提供。要安装它,只需在 Podfile 中添加以下行

pod 'ADCoordinator'

架构

每个协调器都是通过与它“不拥有”的图形上下文实例化的。例如,协调器可以用 UIWindowUINavigationController 初始化。

class ApplicationCoordinator: Coordinator {
    private unowned var navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
}

注意:这里的unowed是必须的,以避免保留UIKit对象。

然后,协调器负责在这个图形上下文中创建和显示视图控制器。

按照惯例,我们使用名为start的方法告诉协调器创建它的第一个视图控制器

class ApplicationCoordinator: Coordinator, MasterViewControllerDelegate {
    ...

    // MARK: - Public

    func start() {
        let viewController = MasterViewController()
        // the view controller can notify the coordinator when it needs to navigate
        viewController.delegate = self
        // display the first view controller in the navigation controller
        navigationController.pushViewController(viewController, animated: false)
        // Automatic memory management
        bindToLifecycle(of: viewController)
    }
}

外部世界负责在屏幕上创建和显示图形上下文。

在这种情况下,AppDelegate将创建一个navigationController并将其设置为其窗口的rootViewController

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var applicationCoordinator: ApplicationCoordinator?

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let window = UIWindow(frame: UIScreen.main.bounds)
        self.window = window

        // Create the graphical context
        let navigationController = UINavigationController()
        // Create the coordinator with the graphical context
        applicationCoordinator = ApplicationCoordinator(navigationController: navigationController)
        // Let the coordinator display its content
        applicationCoordinator?.start()

        // Present the graphical context on screen
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
        return true
    }
}

导航

一旦我们想要改变屏幕上的视图控制器,我们就询问协调器。不允许在协调器外部进行导航。

我们可以水平地推入视图控制器(即在相同的导航堆栈上)或垂直地显示视图控制器(即在另一个导航控制器上,该控制器位于顶部)

水平方向

当协调器接收到屏幕上视图控制器的导航回调时,最简单的方法就是创建一个新的视图控制器并将其推入导航堆栈。

class ApplicationCoordinator: Coordinator, MasterViewControllerDelegate {
    ...

    // MARK: - MasterViewControllerDelegate

    func masterViewControllerDidRequestPush() {
        let detailViewController = DetailViewController()
        navigationController.pushViewController(detailViewController, animated: true)
    }
}

垂直方向

在这种情况下,这意味着创建一个新的图形上下文(通常是导航控制器)并将其显示在顶部。

经验法则:每次调用方法UIViewController.present(_:animated:completion)时都创建一个新的协调器。

class DetailCoordinator: Coordinator {

    private unowned var navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    // MARK: - Public

    func start() {
        let viewController = DetailViewController()
        navigationController.pushViewController(viewController, animated: false)
        // Automatic memory management
        bindToLifecycle(of: viewController)
    }
}

这始终是相同的概念,协调器获得了一个它不拥有的图形上下文并在这个上下文中执行操作。这是创建图形上下文和子协调器的主协调器。

class ApplicationCoordinator: Coordinator, MasterViewControllerDelegate {
    ...

    // MARK: - MasterViewControllerDelegate

    func masterViewControllerDidRequestPresent() {
        // The new graphical context for the child coordinator
        let navigationController = UINavigationController()
        // Create the child coordinator
        let coordinator = DetailCoordinator(navigationController: navigationController)
        // Add child to the tree of coordinators
        addChild(coordinator)
        // Start the coordinator
        coordinator.start()
        // Present the new graphical context on screen
        self.navigationController.present(navigationController, animated: true)
    }
}

内存管理

基础知识

每次创建新的协调器时,都必须由另一个对象保留。根协调器由 AppDelegate 保留,每个协调器都存储一个子协调器数组。协调器可以通过调用 addChild 方法保留子协调器,并通过 removeChild 方法从 children 数组中移除一个子协调器。

class Coordinator {
    private(set) var children: [Coordinator] = []
    private(set) weak var parent: Coordinator?

    func addChild(_ coordinator: Coordinator) { ... }
    func removeChild(_ coordinator: Coordinator) { ... }
}

自动移除子项

从 iOS 13 开始,引入了一种新的模态展示方式。该展示方式并不覆盖整个屏幕,更重要的是,用户可以向下滑动来关闭模态框,而无需回调到视图控制器。

我们之前看到,每个展示的模态都导致创建一个新协调器。一旦模态被关闭,相关的协调器应该被释放。如果用户点击按钮,将动作与该按钮关联,视图控制器可以将意图转发给正确的协调器,调用 removeChild。但是如果用户在 iOS 13 上向下滑动模态框,不会触发任何动作。

为了解决此问题,每个协调器都应该调用 bindToLifecyle(of:),并将它所绑定的视图控制器作为参数传递。

class DetailCoordinator: Coordinator {
    ...

    // MARK: - Public

    func start() {
        let viewController = DetailViewController()
        navigationController.pushViewController(viewController, animated: false)
        bindToLifecycle(of: viewController)
    }
}

此方法将观察 viewController 的生命周期,一旦它被释放,将要求父协调器自动调用 removeChild。这样,子协调器将在它的图形上下文释放时一起被释放。

注意:相同的模式可以应用于水平导航中,当你想要将重复的代码提取到一个协调器中时。当用户在导航栏中点击返回按钮时,你的应用不会触发任何动作。因此,你必须使用 bindToLifecycle(of:) 来观察你想要返回的视图控制器。

使用 bindToLifecycle(of:)

选择正确的对象传递给 bindToLifecycle(of:) 是良好内存管理的关键。如果协调器以导航控制器初始化,你应该观察协调器推入堆栈的第一个视图控制器。这样

  • 当导航控制器被释放时,视图控制器也会被释放,协调器会得到通知
  • 如果视图控制器不是堆栈中的第一个,并且用户执行返回操作,视图控制器将被释放,协调器再次得到通知
  • 如果你想更改导航控制器堆栈,你可以使用另一个视图控制器来调用 bindToLifecycle(of:),以移除之前的观察者并创建一个新的观察者。

作者

许可证

ADCoordinator遵循MIT许可证。详细信息请参阅LICENSE文件。