SwiftTypedRouter
为 SwiftUI 提供的强类型路由
库的目标
- Swift是强类型。我们能把路由库做到多强类型化?
- 单一位置用于自定义路由
- 即使运行代码的是另一个库(即如果我们有一些共享代码,我们应该能够配置其流程),路由也应该是可自定义的
安装
这是一个 Cocoapod。将 pod 'SwiftTypedRouter'
添加到您的 podfile 中。更多信息请见 https://cocoapods.org.cn。查看本存储库中的示例应用,了解它作为 pod 的实际应用。
您也可以简单地查看它并复制文件到您的应用中,如果喜欢的话。
Carthage / SPM 的支持是 issues - 欢迎获取它们并提交 PR ;)
基本使用
设置
启动时(可能是在你的 SceneDelegate
中)使用 Router
注册路由
let router = Router()
router.add("home") { HomeView() }
router.add("product/list/:cat/:num") { (category: String, page: Int) in
ProductListView(category, page)
}
router.add("product/details/:id") { (id: String) in
ProductDetailsView(id: id)
}
并将路由实例提供给视图使用
yourFirstView
.withRouter(router)
使用
给定一个路径,如果匹配不到,则路由器将返回一个视图(或找不到匹配项目时,将返回自己的404视图)
NavigationLink(destination: router.view("product/list/hats/0")) {
Text("See a list of Hats")
}
您还可以在路由器匹配路径之前先检查是否匹配。注意:这比检查 view(_:) != nil
更快
if router.canMatch("product/list/hats/0") {
NavigationLink(destination: router.view("product/list/hats/0")) {
Text("See a list of Hats")
}
} else {
Text("No Hats For You")
}
高级用法
基本用法传递很多字符串。这没问题,但您可以使用更强类型化的值集使用该路由器。
模板
在幕后,调用 router.add("product/details/:id")
是使用所传递的路径和操作块的参数创建一个 Template
类型的操作。您可以使用 TemplateFactory
创建一个 Template
。
extension Template {
static let productDetails = TemplateFactory.start().path("product", "details").placeholder("id", String.self).template()
}
当向路由器添加操作时,您可以使用它。
// Old
// router.add("product/details/:id") { (id: String) in
// With template
router.add(Template.productDetails) { id in
ProductDetailsView(id: id)
}
到目前为止,这并没有太多的好处。
您还可以使用模板来创建传递给路由器的路径 - 这些将是类型安全的。
extension Path {
static func productDetails(id: String) -> Path { Template.productDetails.path(id) }
}
现在,当您使用路由器查找视图时,您会这样做
// Old
// NavigationLink(destination: router.view("product/details/123456")) {
NavigationLink(destination: router.view(.productDetails(id: "123456"))) {
Text("See Product Details")
}
设置起来稍微有些复杂,但您只需键入一次路径,编译器就会为您执行所有的合理性错误检查。
别名
有时,你想在更细粒度的层面上配置你的应用程序,即你希望product/details/123导航到产品详情页面,但你想覆盖特定UI元素的目标位置。
如果你有访问源视图代码的权限,那么这很简单——只需编辑文件即可。然而,如果你的视图位于库中,你可能希望配置多个应用程序具有不同的行为。别名允许你这样做。
别名将一个路径重定向到另一个路径,并具有更丰富的上下文。
例如,如果我们在含有产品列表的屏幕上有一个+按钮,我们会这样做:
- (可选) 创建一个上下文类型(如果你想,可以是
Void
)
struct ProductListAliasContext {
let category: MyCategoryType
}
- 创建一个别名——它的标识符可以是任何你想要的,但使其唯一
let productListPlusTapAlias = Alias<ProductListAliasContext>("product.list.plus.tap")
- 配置你的路由器将别名重定向到正确的路径
router.alias(productListPlusTapAlias) { context in
if context.category == "hats" {
return Path.specialHatHandlingView
} else {
return Path.normalProductAddView
}
}
- 在视图中直接使用别名而不是路径
var context: ProductListAliasContext { ProductListAliasContext(category: self.categpory) }
...
NavigationLink(destination: router.view(productListPlusTapAlias, context: context))) {
Text("+")
}
NB 如果你不想使用上下文,定义你的别名为Alias<Void>("some.identifier")
,你不需要传递上下文参数。