测试已测试 | ✓ |
语语言 | SwiftSwift |
许可协议 | MIT |
发布最后发布 | 2017年2月 |
SwiftSwift 版本 | 3.0 |
SPM支持 SPM | ✗ |
由 Michal Ciurus 维护。
这个库仍在开发中
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
。
例如
• 一个拥有子控制器 UINavigationController
的 UITabBarController
,每个标签都可以在顶部推送 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 操作都会在这里发生,例如 presentViewController
、pushViewController
、addSubview
。
您只需确保在控制您的应用导航动作的对象中遵守 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()
}
}