MonarchRouter
一个轻量但强大的功能状态路由器,用Swift编写。
使用常见的URL约定进行路由。如果您曾经开发过服务器端应用路由,这将使您感到宾至如归。
Monarch Router 是一个声明式路由处理程序,它通过协调器和演示器将ViewControllers分离。它非常适合与Redux风格的状态流和响应式框架配合使用。
协调器是通过声明一个与URL结构相对应的路由层次结构构建的。演示者抽象了UI的创建和修改。
Monarch Router 通过 SPM 和 Cocoapods 分发。
帝王蝶重不到1克,但在迁徙过程中能覆盖数千英里。它被认为是一种标志性的授粉者和最美丽的蝴蝶种类之一。
特性
- 在路径更改时导航复杂的ViewControlles层次结构并展开。
- 根据URL约定解析和传递路由参数到端点。
- 深链接处理推送通知、快捷方式和通用链接。
- 导航分支(类似于标签页的演示器)。
- 导航堆叠(例如导航控制器)。
- 打开和关闭模态,具有自己的层次结构。
- 通过更改窗口的根ViewController来切换顶级应用程序部分。
- 场景处理。
- 处理通用应用程序中的导航。(欢迎PR!)
- 正确地抽象路由层以处理macOS应用程序中的导航。
术语表
-
Router
:您的应用的路由协调器(具有子节点的根RoutingNode
);或者更广泛地说,整个系统。 -
RoutingNode
:收集与相同端点或中间路由点相关的函数的结构,具有子节点。每个RoutingNode
还需要一个Presenter
,任何所需更改都传递给它。 -
RoutePresenter
:用于创建和配置Presentable
(即UIViewController
)的结构。有多种类型的演示者:端点、堆栈(用于导航树)、分歧(用于标签)、开关(用于解耦应用程序部分)。 -
Presentable
:实际要显示的对象(即UIViewController
)。 -
懒惰演示者:一个围绕演示者创建函数的懒惰包装器,它包装演示者作用域,但仍未在调用之前创建
Presentable
。 -
RoutingRequest
:用于定义您想要导航到的端点的URL或类似于URL的结构。 -
Route
:用于定义与RoutingRequest
匹配规则的元素,以触发将路由到特定的RoutingNode
。 -
RouterStore
:保留路由器的状态。提供了一种通过还原器调度RoutingRequest
并修改状态的方法。 -
RouterState
:保留活跃的RoutingNode
的堆栈。 -
RouterReducer
:是一个计算新状态的函数。通过RoutingNode
的回调实现导航,取消未使用的RoutingNode
。
基本流程
- 在
RouterStore
上分发RouteRequest
。请求是一个URL或类似于URL的结构。 - 通过还原器计算新状态,将请求与协调器层次结构匹配。层次结构中的每个节点都与一个
Route
(匹配规则)和一个抽象UI的Presenter
相关联。 - 取消使用中的节点及其相应的演示者,根据计算出的状态重新创建新的演示者层次结构。
示例
示例项目说明了路由器的基本用法,以及一些非平凡用法,例如处理模态和深度链接。
如果您更喜欢使用Cocoapods而不是SPM,请从Example目录克隆存储库,然后首先从Example目录运行pod install
。
查看示例应用。
如何使用
RouterStore
开始。
0. 从创建在您的App或SceneDelegate中持久化它。
// Initializing Router and setting root VC
let coordinator = appCoordinator()
let router = RouterStore(router: coordinator)
self.appRouter = router
Routes
。
1. 定义您的App的Routes是用于与RoutingRequest
进行匹配的规则。
/// Your app custom Routes
enum AppRoute: String, RouteType
{
case login = "login"
case today = "today"
case story = "today/story/:type/:id"
case books = "books"
case book = "books/:id"
}
一个route由RouteComponent
组成。这些组件与RouteRequest
的PathComponent
匹配(见下文)。
定义Route
的方法有几种:
String
符合RouteType
。
- 组件使用
/
分隔。 - 常量组件只是字符串(例如
login
)。 - 参数组件以
:
为前缀。 - 要为组件匹配任何内容,请使用
:_
。 - 要匹配路径的末尾的所有内容,请使用
...
。
RouteString
结构来创建正则表达式验证的route。
使用内置的typealias ParameterValidation = (name: String, pattern: String)
init(_ predicate: String, parametersValidation: [ParameterValidation]? = nil)
- 使用上述规则设置谓词字符串。
- 可选地添加一个
ParameterValidation
数组,其中name
是参数名(不带:
),pattern
是一个正则表达式。
RouteComponent
数组组成的项目符合RouteType
。
由使用RouteComponent
枚举来构建您的Route
。
enum RouteComponent
{
/// Matches a constant component
case constant(String)
/// Matches a parameter component
/// - parameter name: parameter name to match
/// - parameter isMatching: optional closure to match parameter value
case parameter(name: String, isMatching: ((_ value: Any) -> Bool)? )
/// Matches any path component for a route component
case anything
/// Matches any path to the end
case everything
}
RoutingRequest
。
1.1. 可选地定义一组 RoutingRequest
与与 RoutingNode
相关的 Route
进行匹配。
为了方便起见,Monarch Router 使用 URL
或有效的类似 URL 的 String
触发路由。
可用的 URL 部分
- 路径组件 (
books/:id
) - 查询项 (
?name=eliah
) - 片段 (
#documentation
)
您可以直接派发 URL
或 String
。或者您可以创建一个自定义枚举
enum AppRoutingRequest: RoutingRequestType
{
case login
case today
case story(type: String, id: Int, title: String)
case books
case book(id: Int, title: String?)
var request: String {
switch self {
case .login:
return "login"
case .today:
return "today"
case .story(let type, let id, let title):
return "today/story/\(type)/\(id)?title=\(title)"
case .books:
return "books"
case .book(let id, let title):
return "books/\(id)?title=\(title ?? "")"
}
}
如果您为了方便已经决定定义一个自定义的 RoutingRequestType
枚举,您需要一个解析函数。由于在这里我们正在将我们的请求映射到 String
,我们将使用其内置解析器。
func resolve(for route: RouteType) -> RoutingResolvedRequestType {
return request.resolve(for: route)
}
匹配的展示器可以使用解析的 RouteParameters
对象进行参数化(见下文)。
虽然您可以配置基于查询参数和片段的展示器,但只有路径参数用于匹配。
RouterStore
对象上派发路由请求
1.2. 在 router.dispatch(.login)
您可能希望将 RouterStore
实现隐藏在一个专门的 ProvideRouteDispatch
协议后面,即
protocol ProvidesRouteDispatch: class
{
/// Extension method to change the Route.
/// - parameter request: `AppRoutingRequest` to navigate to.
func dispatch(_ request: AppRoutingRequest)
}
extension RouterStore: ProvidesRouteDispatch {
func dispatch(_ request: AppRoutingRequest) {
dispatch(request.request)
}
}
但首先我们需要创建一个协调器。
2. 创建应用程序的协调器
协调器是一个层级的 RoutingNode
结构。
/// Creating the app's Coordinator hierarchy.
func appCoordinator() -> RoutingNodeType
{
return
// Top level app sections' switcher
RoutingNode(sectionsSwitcherRoutePresenter()).switcher([
// Login
// (section 0)
RoutingNode(lazyPresenter(for: .login))
.endpoint(AppRoute.login),
// Tabbar
// (section 1)
RoutingNode(lazyTabBarRoutePresenter()).fork([
// Today nav stack
// (tab 0)
RoutingNode(lazyNavigationRoutePresenter()).stack([
// Today
RoutingNode(lazyPresenter(for: .today))
.endpoint(AppRoute.today, modals: [
// Story
// (presented modally)
RoutingNode(lazyPresenter(for: .story))
.endpoint(AppRoute.story)
])
]),
// Books nav stack
// (tab 1)
RoutingNode(lazyNavigationRoutePresenter()).stack([
// Books
// (master)
RoutingNode(lazyPresenter(for: .books))
.endpoint(AppRoute.books, children: [
// Book
// (detail)
RoutingNode(lazyPresenter(for: .book))
.endpoint(AppRoute.book)
])
])
])
])
}
根据其 Presenter
,一个 RoutingNode
可以执行四种类型的行为之一
- 端点
- 堆栈(即导航树)
- 分支(即标签页)
- 切换器(解耦应用程序的部分)
每个 RoutingNode
都会将其 RoutingRequest
与其 Route
进行匹配(即 .endpoint(AppRoute.today)
),或者与子路由进行匹配(非端点类型的节点)。然后选择合适的子层次结构,并将 RouterState
简化到新的一个。
然后新的节点堆栈的 Presenter
在必要时会实例化其 Presentable
(即 UIViewControler
),自动重建应用程序的导航层次结构。
UI 魔法在展示器中被抽象化。
演讲者
3. 创建 可展示配置
演讲者的主要目标是创建一个 可展示
对象。因此,当您定义一个 演讲者
时,您必须传递一个用于创建可展示的闭包:getPresentable: () -> (UIViewController)
。目前,仅支持 UIViewController
的子类型。
如果使用一些 RouteParameter
调用了 演讲者
,则会调用一个可选的闭包,允许配置 可展示
:setParameters: ((_ parameters: RouteParameters, _ presentable: UIViewController) -> ()
。
注意:使可展示符合
RouteParametrizedPresentable
,以自动处理此功能。
当节点不再被选择时,将调用一个可选的闭包 unwind: (_ presentable: UIViewController) -> ()
。如果您正在进行特殊处理,请设置它。
重要:每个 演讲者
都可以直接或延迟实例化。建议在协调器层次结构中使用延迟初始化,否则所有可展示对象都将应用于应用启动时。
内置演讲者
RoutePresenter
用于端点展示。
端点演讲者能够使用自己的层次结构展示和 dismissing 模态。相应的闭包会被调用。
/// Callback executed when a modal view is requested to be presented.
presentModal: (_ modal: UIViewController, _ over: UIViewController) -> ()
/// Callback executed when a presenter is required to close its modal.
dismissModal: ((_ modal: UIViewController)->())
模态展示是默认的,因此您可能只想使用它们来实现特殊行为。
RoutePresenterFork
用于标签栏样式的展示。
使用特殊的闭包来配置可展示对象(例如 UITabBarController
)
/// Sets the options for Router to choose from
setOptions: (_ options: [UIViewController], _ container: UIViewController) -> ()
/// Sets the specified option as currently selected.
setOptionSelected: (_ option: UIViewController, _ container: UIViewController) -> ()
在通过根路由切换到标签页时使用
.junctionsOnly
调度选项,如果标签页已经包含展示的堆栈。
RoutePresenterStack
用于组织导航堆栈中的其他展示器(例如,UINavigationController
)。
/// Sets the navigation stack
setStack: (_ stack: [UIViewController], _ container: UIViewController) -> ()
/// Presets root Presentable when the stack's own Presentable is requested
prepareRootPresentable: (_ rootPresentable: UIViewController, _ container: UIViewController) -> ()
RoutePresenterSwitcher
用于在解耦的应用部分之间切换(例如,登录序列,主序列等)
/// Sets the specified option as currently selected.
setOptionSelected: (_ option: UIViewController) -> ()
此展示器可能没有展示器。
示例展示器
示例应用包含几个有用的展示器,不是作为库的一部分,比如:
UITabBarController
展示器基于RoutePresenterFork
构建,带有一个委托来处理点击时的路由请求。UINavigationController
展示器基于RoutePresenterStack
构建,有相关的弹出/推送等行为。- 部分切换展示器基于
RoutePresenterSwitcher
构建,可以设置窗口
的rootViewController
。
原理概念
UI是状态的表示
随着状态随时间变化,UI的状态表示也随之变化。对于任何状态值,UI必须是可预测和可重复的。
设备相关状态应与路由状态分开。
例如,在手机和平板上显示相同的状态可能会导致不同的用户界面。设备相关状态应保留在该设备上。OS X和iOS应用可以使用相同的状态和逻辑类以及交换路由来表示状态。
尚未完全实现。欢迎PR!
UI可以生成操作以更新状态中的节点栈
用户点击返回按钮很容易捕获并生成一个更新状态的操作,这会导致用户界面发生变化。但用户“滑动”返回视图就很难捕获了。它应该在完成时生成一个操作以更新状态。然后,如果当前用户界面已经匹配新状态,则不需要进行用户界面更改。
安装
Swift包管理器
使用Xcode UI:转到您的项目设置 -> Swift Packages,然后在那里添加 [email 保护的邮件地址。
要使用Apple的Swift包管理器集成,不带Xcode集成,请将以下内容作为依赖项添加到您的Package.swift中
.package(url: "[email protected]:nikans/MonarchRouter.git", .upToNextMajor(from: "1.1.0"))
CocoaPods
要安装它,只需将以下行添加到您的 Podfile 中
pod 'MonarchRouter', '~> 1.1'
您可以在这里找到最后一个发布版本。
需求
当前仅支持 iOS/iPhone 8.0 及以上版本,但从理论上讲,它很容易扩展以支持通用应用程序。支持 macOS 需要使用泛型等抽象层的新层,我认为现在直接使用它会更清晰。不过,您也可以积极贡献!
- iOS/iPhone
- iOS/通用
- macOS
作者
Eliah Snakin: [email protected]
Monarch Router 是从 Featherweight Router 的晶体中诞生的。
许可证
MonarchRouter 可在 MIT 许可证下使用。有关更多信息,请参阅 LICENSE 文件。