【问题标题】:macOS SwiftUI Navigation for a Single View用于单个视图的 macOS SwiftUI 导航
【发布时间】:2020-08-08 23:05:55
【问题描述】:

我正在尝试为我的 macOS SwiftUI 状态栏应用程序创建一个设置视图。到目前为止,我的实现一直使用NavigationViewNavigationLink,但是当设置视图将父视图推到一边时,此解决方案会产生半视图。截图和代码示例如下。

导航侧边栏

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 上的“全屏”NavigationView (StackNavigationViewStyle) 在 macOS 上不可用。

在 macOS 的 SwiftUI 中,是否有一种简单甚至复杂的方式来实现到设置视图的转换?如果没有,是否可以使用AppKit 来调用用SwiftUI 编写的View 对象?

也是一个 Swift 新手 - 请温柔一点。

【问题讨论】:

    标签: macos navigation swiftui macos-catalina swiftui-navigationview


    【解决方案1】:

    这里是自定义导航类解决方案的可能方法的简单演示。使用 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")
            }
        }
    }
    

    【讨论】:

    • 啊,一个简单有效的解决方案。谢谢一百万!
    • 这是一个很好的解决方案。知道如何阻止 .animation() 修饰符也适用于所有子视图吗?
    • Asperi,你是最棒的。这就是我一直在寻找的
    • 这种方式可以传数据List->Detail吗?
    【解决方案2】:

    我已经扩展了 Asperi 的好建议,并为 macOS(或者甚至是 iOS,如果你愿意的话)创建了一个通用的、可重复使用的 StackNavigationView。一些亮点:

    • 它支持任意数量的子视图(在任何布局中)。
    • 它会自动为每个子视图添加一个“返回”按钮(现在只是文本,但如果使用 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 包装器。

    【讨论】:

    • 这种方式可以传数据List->Detail吗?
    【解决方案3】:

    嘿嘿,我遇到的一个问题是我想拥有多个导航视图层,我不确定这是否也是你的尝试,但如果是的话:MacOS 不继承导航视图。 这意味着,您需要为您的 DetailView(或在您的情况下为 SecondView)提供它自己的 NavigationView。所以,像[...], destination: NavigationView { SecondView() }) [...] 这样嵌入就可以了。

    但是,小心!对 iOS 目标执行相同操作将导致意外行为。因此,如果您同时定位两者,请确保使用 #if os(macOS)!

    但是,在进行设置视图时,我建议您同时查看 Apple 提供的 Settings Scene

    【讨论】:

      【解决方案4】:

      似乎这在 Xcode 13 中没有得到修复。

      在 Xcode 13 Big Sur 上测试,但不是在蒙特雷...

      【讨论】:

        【解决方案5】:

        您可以使用

        获得全屏导航
         .navigationViewStyle(StackNavigationViewStyle())
        

        【讨论】:

        • 啊,要是这么简单,我就会收到构建错误“StackNavigationViewStyle' 在 macOS 中不可用”。
        • 刚刚在 Apple 文档中看到它仅适用于使用 Mac Catalyst 创建的应用程序
        • 这也可以传递数据 LazyGrid() -> Detail for ex?我尝试过但无法完成,因为您需要一次加载两个视图
        • Xcode 13 beta 3 / macOS 12 SDK 尚不支持。不能再等一年了。
        • 我觉得这很有用,不知道风格
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-09
        • 2022-08-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多