为SwiftUI应用提供的一个声明式的路由和导航框架。
注意: 这是一个实验性的框架,生产环境使用需要仔细评估。
注意2: iOS16推出的NavigationStack
比起NavigationView
已经优化了非常多,如果你的应用只需要支持iOS16及以上,建议优先考虑NavigationStack
优点:
- 中心化、声明式的路由定义
- 更简单易用的页面跳转接口
- 支持push/present
- 支持replace
- 支持多级返回
- 支持自定义转场动画
- 页面间无耦合
- 基于字符串的sceneName跳转,更灵活,方便配置化
- 纯SwiftUI实现
缺点:
- 使用
ZStack
自定义的页面栈,存在一些限制:- 不兼容原生导航栈(
NavigationView
) - 需要自定义导航栏
- 不兼容原生
Transition
,自定义动画需要自行插值计算,比较麻烦(当页面中存在List
时跟Transition
有冲突) - 其它未知问题
- 不兼容原生导航栈(
SwiftUI是一个优秀的声明式UI框架,但官方提供的NavigationView
是个非常僵硬的命令式路由系统,起始页需要完整构造出落地页的View才能进行跳转,页面间耦合太重,并且缺少很多能力,并不足以支持一个中大型应用的开发。
因此我设计了SwiftUINavigator
,希望提供一个更契合SwiftUI这种声明式UI思想的路由框架,并提供更加完善的导航能力。然而由于SwiftUI的能力限制,最终实现完全抛弃了原生导航,使用了基于ZStack
的自定义页面栈方案,此方案的可靠性目前还有待验证。
这里提供了一些例子来演示如何利用SwiftUINavigator
来提供路由能力。
Simulator.Screen.Recording.-.iPhone.13.-.2022-10-07.at.21.26.39.mp4
首先,声明路由。
- Router是此路由框架的根节点,它会初始化所需的路由环境。通常情况下Router应该在一个应用的最外层。
- NavScene代表一个页面,只有当前页面的name与其匹配时才会渲染其内容
@main
struct CaseStudyApp: App {
var body: some Scene {
WindowGroup {
Router(initialName: HomeSceneName) {
NavScene(HomeSceneName) {
HomeScene()
}
NavScene(NavActionSceneName) { properties in
let tagStr = properties["tag"] ?? "0"
NavActionScene(tag: Int(tagStr) ?? 0)
}
NavScene(FadeSceneName) {
FadeScene()
}.fadeTransition()
NavScene(PopupSceneName) {
PopupScene()
}
NavScene("*") {
UnknownScene()
}
}
}
}
}
之后,开发scene。scene中可以获取到3个EnvironmentObject:
- Navigator : 路由控制器,通过Navigator控制页面的进出等
- NavigationState : 路由状态,通过NavigationState可以访问完整的页面栈
- NavigateSceneState : 页面状态,通过NavigateSceneState可以访问当前页面的状态
let NavActionSceneName = "nav_action"
struct NavActionScene: View {
@EnvironmentObject private var navigator: Navigator
@EnvironmentObject private var navigationState : NavigationState
@EnvironmentObject private var sceneState : NavigateSceneState
var tag : Int = 0
var body: some View {
var fullPath = ""
for sceneState in navigationState.historyStack {
fullPath += "/\(sceneState.sceneName)"
}
return VStack() {
NavBar(title: "NavAction-\(tag)")
Text("currentFullPath: \(fullPath)")
Button("push") {
navigator.push(NavActionSceneName,properties: ["tag": String(tag + 1)])
}
Button("present") {
navigator.present(NavActionSceneName,properties: ["tag": String(tag + 1)])
}
Button("replace") {
navigator.replace(NavActionSceneName,properties: ["tag": String(tag + 1)])
}
Button("backToHome") {
navigator.goBack(total: .max, animated: true)
}
Spacer()
}.background(sceneState.navigationType == .Push ? .white : .gray)
.offset(x: 0, y: sceneState.navigationType == .Push ? 0 : 150)
}
}
更详细的使用方式请参考例子
- iOS 14.0+ / macOS 12.0+ / tvOS 14.0+ / watchOS 8.0+
You can add SwiftUINavigator to an Xcode project by adding it as a package dependency.
欢迎提Issue和PR,也可以通过邮箱(javanli@qq.com)联系我。