SwiftUI 的高级导航支持。
Navigator 为 SwiftUI 提供了一个简单而强大的导航层,基于 NavigationStack。
这不是另一个简单的推送/弹出导航堆栈库。它支持...
Navigator 完全用 Swift 和 SwiftUI 编写,并支持 iOS 16 及更高版本。
目的地(或路由)通常只是枚举值的公共列表,每个值对应一个所需的视图。
public enum HomeDestinations {
case page2
case page3
case pageN(Int)
}
SwiftUI 要求导航目标值是 Hashable
,我们也是如此。
接下来,我们扩展每个目标,添加一个变量,该变量返回每种情况的正确视图。
extension HomeDestinations: NavigationDestination {
public var view: some View {
switch self {
case .page2:
HomePage2View()
case .page3:
HomePage3View()
case .pageN(let value):
HomePageNView(number: value)
}
}
}
请注意,可以使用关联值将参数传递给所需的视图。
要构建具有外部依赖项或需要访问环境变量值的视图,请参阅下面的 高级目的地
。
与传统的 NavigationStack
目标类型一样,NavigationDestination
类型需要在使用 navigate(to:)
呈现和标准 NavigationLink(value:label:)
转换之前,在封闭的导航堆栈中注册。
但是,由于每个 NavigationDestination
已经定义了要提供的视图,因此可以使用简单的单行视图修饰符来注册目标类型。
ManagedNavigationStack {
HomeView()
.navigationDestination(HomeDestinations.self)
}
这也可以更轻松地将同一目标类型用于多个导航堆栈。
完成上述操作后,可以使用标准的 SwiftUI NavigationLink(value:label:)
视图来分发导航目的地。
NavigationLink(value: HomeDestinations.page3) {
Text("Link to Home Page 3!")
}
或者可以使用修饰符以声明方式分发它们。
// Sample using optional destination
@State var page: SettingsDestinations?
...
Button("Modifier Navigate to Page 3!") {
page = .page3
}
.navigate(to: $page)
// Sample using trigger value
@State var triggerPage3: Bool = false
...
Button("Modifier Trigger Page 3!") {
triggerPage3.toggle()
}
.navigate(trigger: $triggerPage3, destination: SettingsDestinations.page3)
或者通过要求 Navigator 执行所需的操作以命令方式分发它们。
@Environment(\.navigator) var navigator: Navigator
...
Button("Button Navigate To Home Page 55") {
navigator.navigate(to: HomeDestinations.pageN(55))
}
Button("Button Push Home Page 55") {
navigator.push(HomeDestinations.pageN(55))
}
如果您想知道,调用 push
会将关联的视图推送到当前的 NavigationStack
上,而 Navigate(to:)
将根据指定的 NavigationMethod
(接下来介绍)推送或呈现该视图。
可以扩展 NavigationDestination
,为每个枚举类型提供不同的 NavigationMethod
。
extension HomeDestinations: NavigationDestination {
public var method: NavigationMethod {
switch self {
case .page3:
.sheet
default:
.push
}
}
}
在这种情况下,如果调用 navigator.navigate(to: HomeDestinations.page3)
,Navigator 将自动在 sheet 中呈现该视图。所有其他视图都将被推送到导航堆栈上。
当前的导航方法有:.push (默认), .sheet, .cover, 和 .send。
可以使用 Navigator 的 navigate(to:method:)
函数覆盖预定义的方法。
Button("Present Home Page 55 Via Sheet") {
navigator.navigate(to: HomeDestinations.pageN(55), method: .sheet)
}
请注意,通过 NavigationLink 分发的目标始终会推送到 NavigationStack 上。这就是 SwiftUI 的工作方式。
与大多数基于 NavigationStack 的系统一样,Navigator 支持诸如返回到上一个视图、关闭呈现的视图等操作。
Button("Pop To Previous Screen") {
navigator.pop()
}
Button("Dismiss Presented View") {
navigator.dismiss()
}
但这些都是命令式操作。虽然可以以编程方式弹出和关闭屏幕,但这种方法存在问题且往往很脆弱。可以将绑定传递到树下,但这也可能很麻烦且难以维护。
幸运的是,Navigator 支持检查点;导航堆栈中可以轻松返回的命名点。
检查点易于定义和使用。让我们创建一个名为“home”的检查点,然后使用它。
extension NavigationCheckpoint {
public static let home: NavigationCheckpoint = "home"
}
struct RootHomeView: View {
var body: some View {
ManagedNavigationStack(scene: "home") {
HomeContentView(title: "Home Navigation")
.navigationCheckpoint(.home)
.navigationDestination(HomeDestinations.self)
}
}
}
定义后,它们很容易使用。
Button("Return To Checkpoint Home") {
navigator.returnToCheckpoint(.home)
}
.disabled(!navigator.canReturnToCheckpoint(.home))
触发时,检查点将关闭任何呈现的屏幕并弹出任何推送的视图,以返回完全到所需的位置。
检查点也可用于将值返回给调用者。
// Define a checkpoint with a value handler.
.navigationCheckpoint(.settings) { (result: Int) in
returnValue = result
}
// Return, passing a value.
Button("Return to Settings Checkpoint Passing Value 5") {
navigator.returnToCheckpoint(.settings, value: 5)
}
检查点是一个强大的工具。 使用它们。
Navigator 支持外部深度链接和通过导航发送实现的内部应用程序导航。
当导航意味着需要更改非基于 NavigationStack 的值(例如选定的选项卡),或者可能是一个用于触发 NavigationSplitView
中详细视图的帐号时,这会派上用场。
考虑以下相当标准的 RootTabView。
struct RootTabView : View {
@SceneStorage("selectedTab") var selectedTab: RootTabs = .home
var body: some View {
TabView(selection: $selectedTab) {
RootHomeView()
.tabItem { Label("Home", systemImage: "house") }
.tag(RootTabs.home)
RootSettingsView()
.tabItem { Label("Settings", systemImage: "gear") }
.tag(RootTabs.settings)
}
.onNavigationReceive { (tab: RootTabs) in
if tab == selectedTab {
return .immediately
}
selectedTab = tab
return .auto
}
}
}
锐利的眼睛可能已经发现了 onNavigationReceive
修饰符,它 - 非常像 navigationDestination(MyType.self)
- 正在监听 Navigator 广播 RootTabs 类型的值。
收到后,Navigator 将关闭任何呈现的屏幕,设置选定的选项卡,然后正常返回。
使用 navigationSend()
广播值,如下所示。
Button("Send Tab Home, Page 2") {
navigator.send(
RootTabs.home,
HomeDestinations.page2
)
}
RootTabs
接收器切换到选定的选项卡,然后类似的 HomeDestinations
接收器将用户发送到第 2 页。
.onNavigationReceive { (destination: HomeDestinations, navigator) in
navigator.navigate(to: destination)
return .auto
}
这种机制使深度链接和内部导航支持变得简单易用。
请注意,上述某些序列非常常见,因此有快捷方式来支持它们。
struct RootTabView : View {
@SceneStorage("selectedTab") var selectedTab: RootTabs = .home
var body: some View {
TabView(selection: $selectedTab) {
...
}
.onNavigationReceive(assign: $tab)
.onNavigationReceive(HomeDestinations.self)
}
}
以上两者执行的操作与先前显示的示例完全相同。
如果我们无法在没有外部依赖项或没有访问环境的情况下构建特定视图怎么办?
很简单。 只需将视图构建委托给标准的 SwiftUI 视图即可!
extension HomeDestinations: NavigationDestination {
public var view: some View {
HomeDestinationsView(destination: self)
}
}
private struct HomeDestinationsView: View {
let destination: HomeDestinations
@Environment(\.homeDependencies) var resolver
var body: some View {
switch self {
case .home:
HomePageView(viewModel: HomePageViewModel(dependencies: resolver))
case .page2:
HomePage2View(viewModel: HomePage2ViewModel(dependencies: resolver))
case .page3:
HomePage3View(viewModel: HomePage3ViewModel(dependencies: resolver))
case .pageN(let value):
HomePageNView(viewModel: HomePageNViewModel(dependencies: resolver), number: value)
}
}
}
在上面的代码中,我们从环境中获取 homeDependencies
解析器,然后使用它来构建我们的视图和视图模型。
请注意,此技术可用于在视图代码中的其他位置构建和使用功能齐全的视图。 考虑一下。
struct RootHomeView: View {
var body: some View {
ManagedNavigationStack(scene: "home") {
HomeDestinations.home()
.navigationDestination(HomeDestinations.self)
}
}
}
将目标作为函数调用从 HomeDestinationsView
中获取完全解析的 HomePageView
和视图模型,完整且准备就绪。
查看 NavigatorDemo 项目,获取有关此依赖项注入机制的更详尽的示例。
单个 README 文件几乎没有触及表面。 幸运的是,Navigator (将)经过全面记录。
请参阅 Navigator 文档。
另请参阅下面的“其他资源”部分。
Navigator 支持 Swift Package Manager。
或者下载源文件并将 Navigator 文件夹添加到您的项目中。
请注意,当前版本的 Navigator 需要 Swift 5.10 或更高版本,并且此版本当前支持的 iOS 最低版本为 iOS 16。
有关 Navigator 的讨论和评论可以在 Discussions 中找到。如果您有什么要说的,或者想保持最新状态,请转到那里。
Navigator 在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。
如果您想支持我对 Navigator、Factory 和我的其他开源项目的工作,请考虑 GitHub 赞助! 存在多个级别以增加支持,甚至可以提供指导和公司培训。
或者您可以请我喝杯咖啡!
Navigator 由 Michael Long 设计、实施、记录和维护,他是一位首席 iOS 软件工程师,也是 Medium 上排名前 1,000 位的技术作家。
Michael 还是 Google 在 2021 年因其在 Resolver 上的工作而获得的 开源同行奖励 获奖者之一。