SwiftfulRouting 是一个原生、声明式的框架,可以在 SwiftUI 应用程序中实现程序化导航。
RouterView.swift -> body
中。AnyRouter.swift
中。示例项目:https://github.com/SwiftfulThinking/SwiftfulRoutingExample
https://github.com/SwiftfulThinking/SwiftfulRouting.git
导入该包
import SwiftfulRouting
在您的视图层级结构的顶部添加一个 RouterView
。 RouterView
会将您的视图嵌入到导航层级结构中,并添加修饰符以支持所有潜在的跳转。
struct ContentView: View {
var body: some View {
RouterView { _ in
MyView()
}
}
}
所有子视图都可以访问 Environment
中的 Router
。
@Environment(\.router) var router
var body: some View {
Text("Hello, world!")
.onTapGesture {
router.showScreen(.push) { _ in
Text("Another screen!")
}
}
}
}
除了依赖 Environment
之外,您还可以将 Router
直接传递到子视图中。 这允许 Router
完全与 View 解耦(对于更复杂的应用程序架构)。
RouterView { router in
ContentView(router: router)
.onTapGesture {
router.showScreen(.push) { router2 in
Text("View2")
.onTapGesture {
router2.showScreen(.push) { router3 in
Text("View3")
}
}
}
}
}
每次 Segue 后,都会创建一个新的 Router 并将其添加到视图层级结构中。 请参阅 AnyRouter.swift
以查看所有可访问的方法。
为了进入框架的视图层级结构,您必须将您的内容包装在 RouterView 中。 默认情况下,您的视图将被包装在导航堆栈中(iOS 16+ 使用 NavigationStack,iOS 15 及以下版本使用 NavigationView)。
addNavigationView
设置为 FALSE
。screens
绑定到现有的堆栈路径。RouterView(addNavigationView: false, screens: $existingStack) { router in
MyView(router: router)
.navigationBarHidden(true)
.toolbar {
}
}
Router 支持所有原生的 SwiftUI 跳转。
// NavigationLink
router.showScreen(.push) { _ in
Text("View2")
}
// Sheet
router.showScreen(.sheet) { _ in
Text("View2")
}
// FullScreenCover
router.showScreen(.fullScreenCover) { _ in
Text("View2")
}
Segue 方法也接受 AnyRoute
作为便利,这使得在您的代码中传递 Route
变得容易。
let route = AnyRoute(.push, destination: { router in
Text("Hello, world!")
})
router.showScreen(route)
所有 segues 都有一个 onDismiss 方法。
router.showScreen(.push, onDismiss: {
// dismiss action
}, destination: { _ in
Text("Hello, world!")
})
let route = AnyRoute(.push, onDismiss: {
// dismiss action
}, destination: { _ in
Text("Hello, world!")
})
router.showScreen(route)
iOS 16+ 使用 NavigationStack,它支持一次推送多个屏幕。
let route1 = PushRoute(destination: { router in
Text("View1")
})
let route2 = PushRoute(destination: { router in
Text("View2")
})
let route3 = PushRoute(destination: { router in
Text("View3")
})
router.pushScreenStack(destinations: [route1, route2, route3])
iOS 16+ 还支持可调整大小的 sheet。
router.showResizableSheet(sheetDetents: [.medium, .large], selection: nil, showDragIndicator: true) { _ in
Text("Hello, world!)
}
其他便利方法
router.showSafari {
URL(string: "https://www.apple.com")
}
屏幕“流”是支持应用程序中动态路由的一种新方法。 当您进入“屏幕流”时,您会将一个 Routes
数组添加到层级结构中。 应用程序将立即跳转到第一个屏幕,然后将剩余的屏幕设置到队列中。
router.enterScreenFlow([
AnyRoute(.fullScreenCover, destination: screen1),
AnyRoute(.push, destination: screen2),
AnyRoute(.push, destination: screen3),
AnyRoute(.push, destination: screen4),
])
这允许开发人员一次设置多个未来的 segues,而无需每个子视图中的屏幕特定代码。 每个子视图的路由逻辑都像“尝试转到下一个屏幕”一样简单。
do {
try router.showNextScreen()
} catch {
// There is no next screen set in the flow
// Dismiss the flow (see below dismiss methods) or do something else
}
使用“流”的好处
简化的逻辑:在大多数应用程序中,路由逻辑与视图紧密耦合(即,当您创建一个屏幕时,您会在代码中声明下一个屏幕必须是什么)。 现在,您可以构建一个屏幕而根本不必担心路由。 只需支持“转到下一个屏幕”或“取消流”(请参阅下面的取消代码)。
AB 测试:每个用户都可以在您的应用程序中看到独特的屏幕流,您不必在每个子视图中编写“if-else”逻辑。
高级控制:您可以从一个方法控制整个流,该方法将更接近于您应用程序的业务逻辑,而不是在 View 本身中。
流上的流:流是完全动态的,这意味着您可以从流中进入流,并且可以在流中取消屏幕(后退-前进-后退)而不会破坏流。
取消一个屏幕。 您还可以使用原生的 SwiftUI 代码取消屏幕,包括向后滑动或 presentationMode
。
router.dismissScreen()
取消推送到堆栈上的所有屏幕。 这会取消屏幕导航堆栈上的每个“推送”(NavigationLink)。 这不会取消 sheet
或 fullScreenCover
。
router.dismissScreenStack()
取消屏幕环境。 这会取消屏幕的根环境(如果有一个要取消),这是调用站点下方最近的“sheet”或 fullScreenCover
。
router.dismissEnvironment()
例如,如果您输入了以下屏幕流,并且您从任何子视图中调用了 dismissEnvironment
,它将取消 fullScreenCover
,进而取消显示在该环境中的每个视图。
router.enterScreenFlow([
AnyRoute(.fullScreenCover, destination: screen1),
AnyRoute(.push, destination: screen2),
AnyRoute(.push, destination: screen3),
AnyRoute(.push, destination: screen4),
])
用于取消“流”的逻辑通常如下所示
do {
try router.showNextScreen()
} catch {
router.dismissEnvironment()
}
或者便利方法
router.showNextScreenOrDismissEnvironment()
将此代码复制并粘贴到您的项目中以启用向后滑动的手势。 默认情况下,这不包含在 SwiftUI 框架中,因此不会自动包含在此处。
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
Router 支持原生的 SwiftUI 警报。
// Alert
router.showAlert(.alert, title: "Title goes here", subtitle: "Subtitle goes here!") {
Button("OK") {
}
Button("Cancel") {
}
}
// Confirmation Dialog
router.showAlert(.confirmationDialog, title: "Title goes here", subtitle: "Subtitle goes here!") {
Button("A") {
}
Button("B") {
}
Button("C") {
}
}
取消警报。
router.dismissAlert()
其他便利方法
router.showBasicAlert(text: "Error")
Router 还支持任何模态转换,该转换显示在当前内容之上。 自定义过渡、动画、背景颜色/模糊等。 有关示例实现,请参见示例项目。
router.showModal(transition: .move(edge: .top), animation: .easeInOut, alignment: .top, backgroundColor: nil, useDeviceBounds: true) {
Text("Sample")
.onTapGesture {
router.dismissModal()
}
}
您可以同时显示多个模态框。 模态框具有一个可选的 ID 字段,稍后可用于取消模态框。
router.showModal(id: "top1") {
Text("Sample")
}
// Dismiss top-most modal
router.dismissModal()
// Dismiss modal by ID
router.dismissModal(id: "top1")
// Dismiss all modals
router.dismissAllModals()
其他便利方法
router.showBasicModal {
Text("Sample")
.onTapGesture {
router.dismissModal()
}
}
我们鼓励社区贡献! 请确保您的代码符合项目现有的编码风格和结构。 大多数新功能可能都是现有功能的派生,因此应重用许多现有的 ViewModifier 和 Bindings。
即将推出的功能
showModule
支持,用于在父级 RouterView 之间导航