BatanaRouter 0.4

BatanaRouter 0.4

测试已测试
语言 SwiftSwift
许可协议 MIT
发布最后发布2017年2月
SwiftSwift 版本3.0
SPM支持 SPM

Michal Ciurus 维护。



BatanaRouter: Katana 的 App 导航路由

这个库仍在开发中

BatanaRouter 是一个用于 Katana 的声明式、类型安全的 App 导航路由器。Katana 的 状态 结构应该代表整个 App 的状态,包括导航。改变导航状态的唯一方式应该是通过 动作

BatanaRouter 会为您处理所有事情:存储状态、提供您改变状态的动作,并找出导航状态之间的差异。

ReSwift-Router 启发.

安装

要求

  • iOS 8.4+ / macOS 10.10+

  • Xcode 8.0+

  • Swift 3.0+

概述

BatanaRouter 完全与任何 UI 框架解耦。底层是一个 Destination 结构的树。每个 Destination 可以包含子 Destination,其中一个可以是 激活的(可见的)。

Destination 可以路由到任何东西,一个 UIViewController,甚至是一个 UIView

例如

• 一个拥有子控制器 UINavigationControllerUITabBarController,每个标签都可以在顶部推送 UIViewControllers。它在一个树中建模,其中标签栏控制器是根节点

            UITabBarController(active)
               +     +   +
   +-----------+     |   +------------+
   |                 |                |
   |                 +                |
   +                                  +
Tab One(active)    Tab Two         Tab Three

   +                  +                +
   |                  |                |
   |                  |                |
   |                  +                |
   +                                   +
ChildVc(active)     ChildVc          ChildVc

• 你可以用相同的方式将具有子 UIView 目标(位于 UIScrollView 中)的 UIViewController 目标建模,并通过动作声明性地修改 UIView 子节点。这意味着您可以使用 BatanaRouter 路由您的子 UIView

目的地

Destination 是一个结构,用于表示一个唯一的 destination,通过唯一的 instanceIdentifier 或通过您设置的以方便以后引用的 userIdentifier 来标识。**请确保所有 userIdentifier 都是唯一的**。

routableType 是符合 Routable 协议的类的类型,该协议将支持所有导航更改回调。

您还可以在内部传递 contextData,以便在导航时接收并使用它。

状态存储

KatanaRouter 负责为您存储当前导航状态。您只需确保您的状态遵循 RoutableState 协议。

struct MyState: State, RoutableState {
    var navigationState: NavigationState = NavigationState()
}

改变状态

KatanaRouter 提供了改变当前状态的操作。如果您需要请求缺少的新状态改变操作,请创建一个 GitHub 上的 issue。

    func didTapPush() {
         //RandomViewController conforms to `Routable` type
        let pushAction = AddNewDestination(destination: Destination(routableType: RandomViewController.self))
        store.dispatch(pushAction)
    }

对状态改变做出反应

KatanaRouter 有一个算法用于找出状态间的差异并向您报告。所有的具体 UI 操作都会在这里发生,例如 presentViewControllerpushViewControlleraddSubview

您只需确保在控制您的应用导航动作的对象中遵守 Routable 协议。

extension RandomViewController: Routable {

Routable 有 4 个方法可以实施

• Push - 单个的 Push 动作发生。您负责 Push/present/show 目的地并返回它的 Routable

    func push(destination: Destination, completionHandler: @escaping RoutableCompletion) -> Routable {
        switch(destination.routableType) {
        case is RandomViewController.Type:
            // Instantiate and push/present/show an instance of `RandomViewController`
            // Remember to always call the `completionHandler`
            completionHandler()
            // Return a `Routable` responsible for the pushed destination
            return randomViewController
        default: fatalError("Not supported")
        }
    }

• Pop - 单个的 Pop 动作发生。您负责 Pop/dismiss/remove 目的地

    func pop(destination: Destination, completionHandler: @escaping RoutableCompletion) {
        switch(destination.routableType) {
        case is RandomViewController.Type:
            // pop/dismiss/remove `randomViewController`
        completionHandler()
        default: fatalError("Not supported")
        }
    }

• Change - 一个更复杂的行为。至少有两次单个的 Pop/Push 动作。它给了您机会在一个平滑的转换中替换目的地。您负责 Pop 和 Push,同时还需要返回所有新 Push 目的地对应的 Routable

        public func change(destinationsToPop: [Destination], destinationsToPush: [Destination], completionHandler: @escaping RoutableCompletion) -> [Destination : Routable] {
        var createdRoutables: [Destination : Routable] = [:]
        for destinationToPush in destinationsToPush {
            switch destinationToPush.routableType {
            case is FirstChildView.Type:
                // Show/add first child
                // You need to return the `Routable` that's responsible for routing the `FirstChildView` instance
                createdRoutables[destinationToPush] = firstChildInstance
            case is SecondChildView.Type:
                // Show/add second child
                // You need to return the `Routable` that's responsible for routing the `SecondChildView` instance
                createdRoutables[destinationToPush] = secondChildInstance
            default: fatalError("Not supported")
            }
        }
        // Remember to always call the CompletionHandler when finished with the transition!
        completionHandler()
        return createdRoutables
    }

• ChangeActiveDestination - 当设置一个新的目的地为活动状态时被调用。您负责使活动的目的地可见。

        public func changeActiveDestination(currentActiveDestination: Destination, completionHandler: @escaping RoutableCompletion) {
        switch currentActiveDestination.routableType {
            case is FirstChildView.Type:
                self.selectedIndex = 0
            case is SecondChildView.Type:
                self.selectedIndex = 1
        default: fatalError("Not supported")
        }
        completionHandler()
    }
}