这个包使用了 SwiftUI 强大且熟悉的 NavigationStack
API,并赋予其更强大的功能,让您不仅可以将相同的 API 用于推送导航,还可以用于呈现 sheet 和全屏覆盖。而且因为它使用了旧版 SwiftUI 中可用的导航 API 实现,所以您甚至可以在更早版本的 iOS、tvOS、watchOS 和 macOS 上使用它。
如果您有以下需求,您可能会喜欢这个库:
✅ 您希望支持深度链接到应用程序中深度嵌套的导航路由。
✅ 您希望在不同的导航上下文中轻松地重复使用视图。
✅ 您希望轻松返回到根屏幕或导航堆栈中的特定屏幕。
✅ 您希望使用协调器模式将导航逻辑保存在一个地方。
✅ 您希望将应用程序的导航分解为多个可重用的协调器并将它们组合在一起。
如果您已经了解 SwiftUI 的 NavigationStack
API,那么 FlowStacks
应该会让您感到熟悉且直观。只需在类型和函数名称中将“Navigation”替换为“Flow”即可。
✅ NavigationStack
-> FlowStack
✅ NavigationLink
-> FlowLink
✅ NavigationPath
-> FlowPath
✅ navigationDestination
-> flowDestination
NavigationStack
的完整 API 均已复制,因此您可以使用绑定到 Array
的绑定、绑定到 FlowPath
的绑定或者根本没有绑定的方式来初始化 FlowStack
。唯一的区别是,数组应该是一个 [Route<MyScreen>]
s 而不是 [MyScreen]
。Route
枚举将目标数据与有关所使用的呈现样式的信包含在一起。类似地,当您创建 FlowLink
时,您还必须指定路由样式,例如 .push
、.sheet
或 .cover
。与 NavigationStack
一样,如果用户点击后退按钮或滑动以关闭 sheet,则路由数组将自动更新以反映新的导航状态。
import FlowStacks
import SwiftUI
struct ContentView: View {
@State var path = FlowPath()
@State var isShowingWelcome = false
var body: some View {
FlowStack($path, withNavigation: true) {
HomeView()
.flowDestination(for: Int.self, destination: { number in
NumberView(number: number)
})
.flowDestination(for: String.self, destination: { text in
Text(text)
})
.flowDestination(isPresented: $isShowingWelcome, style: .sheet) {
Text("Welcome to FlowStacks!")
}
}
}
}
struct HomeView: View {
@EnvironmentObject var navigator: FlowPathNavigator
var body: some View {
List {
ForEach(0 ..< 10, id: \.self) { number in
FlowLink(value: number, style: .sheet(withNavigation: true), label: { Text("Show \(number)") })
}
Button("Show 'hello'") {
navigator.push("Hello")
}
}
.navigationTitle("Home")
}
}
struct NumberView: View {
@EnvironmentObject var navigator: FlowPathNavigator
let number: Int
var body: some View {
VStack(spacing: 8) {
Text("\(number)")
FlowLink(
value: number + 1,
style: .push,
label: { Text("Show next number") }
)
Button("Go back to root") {
navigator.goBackToRoot()
}
}
.navigationTitle("\(number)")
}
}
除了复制新的 NavigationStack
API 的标准功能之外,还添加了一些有用的实用程序。
可以通过环境访问 FlowNavigator
对象,从而可以访问当前的路由数组,并可以通过多种便捷方法更新它。可以通过环境访问导航器,例如,对于一个 FlowPath
支持的堆栈。
@EnvironmentObject var navigator: FlowPathNavigator
或者对于由路由数组支持的 FlowStack,例如 [Route<ScreenType>]
。
@EnvironmentObject var navigator: FlowNavigator<ScreenType>
这是一个使用 FlowNavigator
的示例:
@EnvironmentObject var navigator: FlowNavigator<ScreenType>
var body: some View {
VStack {
Button("View detail") {
navigator.push(.detail)
}
Button("Go back to profile") {
navigator.goBackTo(.profile)
}
Button("Go back to root") {
navigator.goBackToRoot()
}
}
}
与 FlowNavigator
(以及原始 FlowPath
或路由数组)交互时,可以使用多种便捷方法来简化导航,包括:
方法 | 效果 |
---|---|
push | 将新屏幕推送到堆栈上。 |
presentSheet | 将新屏幕显示为 sheet。† |
presentCover | 将新屏幕显示为全屏覆盖。† |
goBack | 返回堆栈中的上一个屏幕。 |
goBackToRoot | 返回堆栈中的第一个屏幕。 |
goBackTo | 返回堆栈中的特定屏幕。 |
pop | 如果已推送,则弹出当前屏幕。 |
dismiss | 关闭最近呈现的屏幕。 |
† 如果您希望能够从呈现的屏幕推送屏幕,请传递 embedInNavigationView: true
。
在引入 NavigationStack
API 之前,SwiftUI 不支持在单个状态更新中推送多个屏幕,例如,当深度链接到导航层次结构中多个层级的屏幕时。FlowStacks 解决了此限制:您可以进行任何此类更改,该库将在后台将较大的更新分解为 SwiftUI 支持的一系列较小的更新,必要时会在两者之间添加延迟。
可以将 flow 目标配置为使用对其路由数组中屏幕状态的绑定,而不仅仅是只读值 - 只需在 flowDestination
函数的视图构建器闭包中的屏幕参数之前添加 $
即可。然后,屏幕本身可以负责更新其在路由数组中的状态,例如。
import SwiftUINavigation
struct BindingExampleCoordinator: View {
@State var path = FlowPath()
var body: some View {
FlowStack($path, withNavigation: true) {
FlowLink(value: 1, style: .push, label: { Text("Push '1'") })
.flowDestination(for: Int.self) { $number in
EditNumberScreen(number: $number) // This screen can now change the number stored in the path.
}
}
}
如果您使用的是类型化的路由数组,您可能正在使用枚举来表示屏幕,因此可能需要进一步提取该枚举的特定情况的关联值作为绑定。您可以使用 SwiftUINavigation 库来完成此操作,该库包含许多有用的可选和枚举状态绑定转换,例如。
import FlowStacks
import SwiftUI
import SwiftUINavigation
enum Screen: Hashable {
case number(Int)
case greeting(String)
}
struct BindingExampleCoordinator: View {
@State var routes: Routes<Screen> = []
var body: some View {
FlowStack($routes, withNavigation: true) {
HomeView()
.flowDestination(for: Screen.self) { $screen in
if let number = Binding(unwrapping: $screen, case: /Screen.number) {
// Here `number` is a `Binding<Int>`, so `EditNumberScreen` can change its
// value in the routes array.
EditNumberScreen(number: number)
} else if case let .greeting(greetingText) = screen {
// Here `greetingText` is a plain `String`, as a binding is not needed.
Text(greetingText)
}
}
}
}
}
struct HomeView: View {
@EnvironmentObject var navigator: FlowPathNavigator
var body: some View {
VStack {
FlowLink(value: Screen.number(42), style: .push, label: { Text("Show Number") })
FlowLink(value: Screen.greeting("Hello world"), style: .push, label: { Text("Show Greeting") })
}
}
}
struct EditNumberScreen: View {
@Binding var number: Int
var body: some View {
Stepper(
label: { Text("\(number)") },
onIncrement: { number += 1 },
onDecrement: { number -= 1 }
)
}
}
FlowStack
旨在可组合,因此您可以拥有多个 flow 协调器,每个协调器都有自己的 FlowStack
,并且您可以从父协调器呈现或推送子协调器。有关更多信息,请参阅 嵌套 FlowStacks。
该库通过将路由数组转换为嵌套 NavigationLinks 和呈现调用的层次结构来实现,扩展了 NavigationBackport 中使用的技术。
请参阅 迁移文档。