单个视图的 macOS SwiftUI 导航
macOS SwiftUI Navigation for a Single View
我正在尝试为我的 macOS SwiftUI 状态栏应用创建设置视图。到目前为止,我的实现一直使用 NavigationView
和 NavigationLink
,但由于设置视图将父视图推到一边,此解决方案会生成半视图。下面的屏幕截图和代码示例。
导航边栏
struct ContentView: View {
var body: some View {
VStack{
NavigationView{
NavigationLink(destination: SecondView()){
Text("Go to next view")
}}
}.frame(width: 800, height: 600, alignment: .center)}
}
struct SecondView: View {
var body: some View {
VStack{
Text("This is the second view")
}.frame(width: 800, height: 600, alignment: .center)
}
}
我能找到的少量信息表明,在 macOS 上使用 SwiftUI 是不可避免的,因为 iOS 上的 'full screen' NavigationView
(StackNavigationViewStyle
) 在 macOS 上不可用。
在 macOS SwiftUI 中,是否有一种简单甚至复杂的方法来实现到占据整个框架的设置视图的转换?如果没有,是否可以使用AppKit
调用SwiftUI中编写的View对象?
也是Swift新手,请温柔点
您可以使用
获得全屏导航
.navigationViewStyle(StackNavigationViewStyle())
这是自定义导航类解决方案的可能方法的简单演示。使用 Xcode 11.4 / macOS 10.15.4
测试
注意:背景色用于提高可见度。
struct ContentView: View {
@State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
@Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
struct NextView: View {
@Binding var show: Bool
var body: some View {
VStack{
Button("Back") { self.show = false }
Text("This is the second view")
}
}
}
我扩展了 Asperi 的伟大建议,并为 macOS 创建了一个通用的、可重复使用的 StackNavigationView
(如果需要,甚至 iOS)。一些亮点:
- 它支持任意数量的子视图(在任何布局中)。
- 它会自动为每个子视图添加一个 'Back' 按钮(目前只是文本,但如果使用 macOS 11+,您可以换成一个图标)。
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 { // Root view
rootView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if showingSubview { // Correct subview for current index
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 { // Back button
Button(action: {
self.isVisible = false
}) {
Text("< Back")
}.buttonStyle(BorderlessButtonStyle())
Spacer()
}
.padding(.horizontal).padding(.vertical, 4)
contentView() // Main view content
}
}
}
}
有关 @ViewBuilder
和所用泛型的更多信息,请参见 here。
这是它的一个基本使用示例。父视图跟踪当前选择和显示状态(使用 @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
包装器。
嗨,我遇到的一个问题是我想要多个导航视图层,我不确定这是否也是您的尝试,但如果是:MacOS 不继承导航视图.
意思是,您需要为您的 DetailView(或您的情况下的 SecondView
)提供它自己的 NavigationView
。所以,像 [...], destination: NavigationView { SecondView() }) [...]
这样的嵌入应该可以解决问题。
但是,小心!对 iOS 目标执行相同操作将导致意外行为。因此,如果您同时定位两者,请确保使用 #if os(macOS)
!
但是,在进行设置视图时,我建议您也查看 Apple 提供的 Settings Scene。
似乎这在 Xcode 13.
中没有得到修复
在 Xcode 13 Big Sur 测试,但未在 Monterrey 测试...
我正在尝试为我的 macOS SwiftUI 状态栏应用创建设置视图。到目前为止,我的实现一直使用 NavigationView
和 NavigationLink
,但由于设置视图将父视图推到一边,此解决方案会生成半视图。下面的屏幕截图和代码示例。
导航边栏
struct ContentView: View {
var body: some View {
VStack{
NavigationView{
NavigationLink(destination: SecondView()){
Text("Go to next view")
}}
}.frame(width: 800, height: 600, alignment: .center)}
}
struct SecondView: View {
var body: some View {
VStack{
Text("This is the second view")
}.frame(width: 800, height: 600, alignment: .center)
}
}
我能找到的少量信息表明,在 macOS 上使用 SwiftUI 是不可避免的,因为 iOS 上的 'full screen' NavigationView
(StackNavigationViewStyle
) 在 macOS 上不可用。
在 macOS SwiftUI 中,是否有一种简单甚至复杂的方法来实现到占据整个框架的设置视图的转换?如果没有,是否可以使用AppKit
调用SwiftUI中编写的View对象?
也是Swift新手,请温柔点
您可以使用
获得全屏导航 .navigationViewStyle(StackNavigationViewStyle())
这是自定义导航类解决方案的可能方法的简单演示。使用 Xcode 11.4 / macOS 10.15.4
测试注意:背景色用于提高可见度。
struct ContentView: View {
@State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
@Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
struct NextView: View {
@Binding var show: Bool
var body: some View {
VStack{
Button("Back") { self.show = false }
Text("This is the second view")
}
}
}
我扩展了 Asperi 的伟大建议,并为 macOS 创建了一个通用的、可重复使用的 StackNavigationView
(如果需要,甚至 iOS)。一些亮点:
- 它支持任意数量的子视图(在任何布局中)。
- 它会自动为每个子视图添加一个 'Back' 按钮(目前只是文本,但如果使用 macOS 11+,您可以换成一个图标)。
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 { // Root view
rootView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if showingSubview { // Correct subview for current index
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 { // Back button
Button(action: {
self.isVisible = false
}) {
Text("< Back")
}.buttonStyle(BorderlessButtonStyle())
Spacer()
}
.padding(.horizontal).padding(.vertical, 4)
contentView() // Main view content
}
}
}
}
有关 @ViewBuilder
和所用泛型的更多信息,请参见 here。
这是它的一个基本使用示例。父视图跟踪当前选择和显示状态(使用 @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
包装器。
嗨,我遇到的一个问题是我想要多个导航视图层,我不确定这是否也是您的尝试,但如果是:MacOS 不继承导航视图.
意思是,您需要为您的 DetailView(或您的情况下的 SecondView
)提供它自己的 NavigationView
。所以,像 [...], destination: NavigationView { SecondView() }) [...]
这样的嵌入应该可以解决问题。
但是,小心!对 iOS 目标执行相同操作将导致意外行为。因此,如果您同时定位两者,请确保使用 #if os(macOS)
!
但是,在进行设置视图时,我建议您也查看 Apple 提供的 Settings Scene。
似乎这在 Xcode 13.
中没有得到修复在 Xcode 13 Big Sur 测试,但未在 Monterrey 测试...