Helm 是一个声明式的、基于图的 SwiftUI 路由库。它完整地描述了应用中所有的导航流程,并且可以处理复杂的重叠 UI、模态框、深度链接以及更多。
在 Helm 中,导航规则在图结构中定义,使用片段(fragments)和 Segues(转场)。片段是应用中的动态部分,有些是屏幕,另一些是重叠视图(例如音乐应用中的滑动播放器)。Segues 是有向边,用于指定两个片段之间的规则,例如呈现样式或 auto 标志(更多关于这些请参见下方)。
与传统的路由器不同,Helm 使用有序的边集合来表示路径。这允许查询已呈现的片段以及到达它们所需的步骤,同时支持多层 UI。路径还可以为每个片段分配一个可选的 ID。这些 ID 用于从同一片段呈现动态数据。(例如,在主-细节列表中,.detail
片段将需要当前呈现的项目的 ID。)
转场封装了从一个片段到另一个片段的导航命令。在 Helm 中有 3 种类型的转场
Helm
,主类,在片段之间导航,返回它们的呈现状态和所有可能的转场等等。它遵循 ObservableObject
协议,可以作为注入的 @EnvironmentObject
使用。
Segues 是片段之间带有导航规则的有向边
style
:.hold
或 .pass
,当从已呈现的片段呈现新片段时,原始片段应该保持其状态还是将其传递给目标片段。简单来说,如果我们希望在转场后两个片段都可见(例如,当您呈现模态框或一般重叠视图时),我们应该使用 .hold
。dismissable
:尝试关闭一个未标记为可关闭的片段将导致错误(例如,一旦用户引导完成,您就无法关闭仪表板并返回到引导屏幕)。auto
:一些容器片段(如标签页)会自动呈现子片段。将一个 segue 标记为 auto 将在其 in
片段到达后立即呈现其 out
片段。tag
:有时通过标签呈现或关闭一个 segue 很方便。涵盖您在 SwiftUI 应用中可能遇到的大多数场景的完整示例可以在这里找到。
我们首先定义应用中的所有片段。
enum Section: Fragment {
// the first screen right after the app starts
case splash
// the screen that contains the login, register or forgot password fragments
case gatekeeper
// the three fragments of the gatekeeper screen
case login
case register
case forgotPass
// and so on ...
}
现在我们有
接下来是导航图。通常我们需要写下每个 segue。
let segues: Set<Segue<Section>> = [
Segue(from: .splash, to: .gatekeeper),
Segue(from: .splash, to: .dashboard),
Segue(from: .gatekeeper, to: .login, auto: true)
Segue(from: .gatekeeper, to: .register)
//...
]
但这可能会变得非常冗长,所以,相反,我们可以使用有向边运算符 =>
来定义所有边,然后将它们转换为 segues。由于 =>
支持一对多、多对一和多对多的连接,我们可以用更少的代码行创建所有边。
let edges = Set<DirectedEdge<Section>>()
.union(.splash => [.gatekeeper, .dashboard])
.union([.gatekeeper => .login])
.union(.login => .register => .forgotPass => .login)
.union(.login => .forgotPass => .register => .login)
.union([.login, .register] => .dashboard)
.union(.dashboard => [.news, .compose])
.union(.library => .news => .library)
let segues = Set(edges.map { (edge: DirectedEdge<Section>) -> Segue<Section> in
switch edge {
case .gatekeeper => .login:
return Segue(edge, style: .hold, auto: true)
case .dashboard => .news:
return Segue(edge, style: .hold, auto: true)
case .dashboard => .compose:
return Segue(edge, style: .hold, dismissable: true)
case .dashboard => .library:
return Segue(edge, style: .hold)
default:
// the default is style: .pass, auto: false, dismissable: false
return Segue(edge)
}
})
现在我们有
一旦我们有了 segues,下一步是创建我们的 Helm
实例。可选地,我们也可以传递一个路径,以便在应用程序启动时从入口片段以外的特定片段开始。请注意,入口片段(在本例中为 .splash
)始终会被呈现。
try Helm(nav: segues)
// or
try Helm(nav: segues,
path: [
.splash => .gatekeeper,
.gatekeeper => .register
])
然后,我们将 Helm
注入到最顶层的视图中
struct RootView: View {
@StateObject private var _helm: Helm = ...
var body: some View {
ZStack {
//...
}
.environmentObject(_helm)
}
}
最后,我们可以使用 Helm 了。请务必查看每个呈现/关闭方法的接口文档,以了解它们之间的区别。
struct DashboardView: View {
@EnvironmentObject private var _helm: Helm<PlaygroundFragment>
var body: some View {
VStack {
HStack {
Spacer()
LargeButton(action: { _helm.present(fragment: .compose) }) {
Image(systemName: "plus.square.on.square")
}
}
TabView(selection: _helm.pickPresented([.library, .news, .settings])) {
LibraryView()
.tabItem {
Label("Library", systemImage: "book.closed")
}
.tag(Optional.some(PlaygroundFragment.library))
NewsView()
.tabItem {
Label("News", systemImage: "newspaper")
}
.tag(Optional.some(PlaygroundFragment.news))
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape.fill")
}
.tag(Optional.some(PlaygroundFragment.settings))
}
}
.sheet(isPresented: _helm.isPresented(.compose)) {
ComposeView()
}
}
}
Helm 的大多数方法都不会抛出错误,而是使用 errors
发布的属性报告错误。这允许与 SwiftUI 处理程序(例如 Button
的 action)无缝集成,同时也使调试和断言变得容易。
_helm.$errors
.sink {
assertionFailure($0.description)
}
.store(in: &cancellables)
呈现路径 (OrderedSet<DirectedEdge<N>>
) 已经符合 Encodable
和 Decodable
协议,因此可以轻松地保存和恢复为 JSON 对象。或者,可以将更简单的字符串路径转换为基于图的呈现路径,并使用前者来链接应用程序中的各个部分。
能够遍历导航图是 Helm 的最大优势之一。这可以有多种用途,其中快照测试是最重要的。遍历,在每个步骤之后拍摄快照,并将结果与先前保存的快照进行比较。所有这些都可以在几行代码中完成
let transitions = _helm.transitions()
for transition in transitions {
try helm.navigate(transition: transition)
// mutate state if needed, take a snapshot, compare it
}
此外,通过使用自定义的转场集合,可以在片段之间进行任意步骤。这可以用于自动记录特定流程的视频(和快照)(对 App Store 宣传材料非常有帮助)。
该软件包包含一个名为 Playground
的额外项目。它将 Helm 与 SwiftUI 集成,包括使用 NavigationView
、sheet 模态框、TabView
等。