我在 Asperi 的建议基础上进行了扩展,并创建了一个通用的、可重用的 StackNavigationView
,适用于 macOS(如果需要,也可用于 iOS)。以下是一些亮点:
- 支持任意数量的子视图(以任何布局方式)。
- 自动为每个子视图添加“返回”按钮(目前只有文本,但如果使用 macOS 11+,可以更换图标)。
![macOS 上的示例视图](https://istack.dev59.com/LCabq.gif)
Swift v5.2:
struct StackNavigationView<RootContent, SubviewContent>: View where RootContent: View, SubviewContent: View {
@Binding var currentSubviewIndex: Int
@Binding var showingSubview: Bool
let subviewByIndex: (Int) -> SubviewContent
let rootView: () -> RootContent
var body: some View {
VStack {
VStack{
if !showingSubview {
rootView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if showingSubview {
StackNavigationSubview(isVisible: self.$showingSubview) {
self.subviewByIndex(self.currentSubviewIndex)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
init(currentSubviewIndex: Binding<Int>, showingSubview: Binding<Bool>, @ViewBuilder subviewByIndex: @escaping (Int) -> SubviewContent, @ViewBuilder rootView: @escaping () -> RootContent) {
self._currentSubviewIndex = currentSubviewIndex
self._showingSubview = showingSubview
self.subviewByIndex = subviewByIndex
self.rootView = rootView
}
private struct StackNavigationSubview<Content>: View where Content: View {
@Binding var isVisible: Bool
let contentView: () -> Content
var body: some View {
VStack {
HStack {
Button(action: {
self.isVisible = false
}) {
Text("< Back")
}.buttonStyle(BorderlessButtonStyle())
Spacer()
}
.padding(.horizontal).padding(.vertical, 4)
contentView()
}
}
}
}
有关使用泛型和 @ViewBuilder
的更多信息,请参见此处。
下面是一个基本示例的用法。父视图跟踪当前选择和显示状态(使用 @State
),允许其子视图中的任何内容触发状态更改。
struct ExampleView: View {
@State private var currentSubviewIndex = 0
@State private var showingSubview = false
var body: some View {
StackNavigationView(
currentSubviewIndex: self.$currentSubviewIndex,
showingSubview: self.$showingSubview,
subviewByIndex: { index in
self.subView(forIndex: index)
}
) {
VStack {
Button(action: { self.showSubview(withIndex: 0) }) {
Text("Show View 1")
}
Button(action: { self.showSubview(withIndex: 1) }) {
Text("Show View 2")
}
Button(action: { self.showSubview(withIndex: 2) }) {
Text("Show View 3")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
}
}
private func subView(forIndex index: Int) -> AnyView {
switch index {
case 0: return AnyView(Text("I'm View One").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.green))
case 1: return AnyView(Text("I'm View Two").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.yellow))
case 2: return AnyView(VStack {
Text("And I'm...")
Text("View Three")
}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.orange))
default: return AnyView(Text("Inavlid Selection").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red))
}
}
private func showSubview(withIndex index: Int) {
currentSubviewIndex = index
showingSubview = true
}
}
注意:像这样的泛型要求所有子视图都是相同类型的。如果不是这样,您可以将它们包装在AnyView
中,就像我在这里所做的那样。如果您对所有子视图使用一致的类型(根视图的类型不需要匹配),则不需要AnyView
包装器。
.animation()
修饰符也应用于所有子视图吗? - TheNeil