簡體   English   中英

macOS SwiftUI 單一視圖導航

[英]macOS SwiftUI Navigation for a Single View

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

是否有一種簡單甚至復雜的方法來實現到設置視圖的轉換,該視圖占用 macOS 的 SwiftUI 中的整個幀? 如果沒有,是否可以使用AppKit調用用 SwiftUI 編寫的 View object?

還有一個 Swift 新手 - 請溫柔一點。

這是自定義導航類解決方案的可能方法的簡單演示。 使用 Xcode 11.4 / macOS 10.15.4 測試

演示

注意:背景 colors 用於更好的可見性。

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(或者甚至 iOS,如果你願意)創建了一個通用的、可重用的StackNavigationView 一些亮點:

  • 它支持任意數量的子視圖(在任何布局中)。
  • 它會自動為每個子視圖添加一個“后退”按鈕(現在只是文本,但如果使用 macOS 11+,您可以交換圖標)。

macOS 上的示例視圖

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和 generics 的更多信息。

這是一個使用中的基本示例。 父視圖跟蹤當前選擇和顯示狀態(使用@State ),允許其子視圖中的任何內容觸發 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
    }
}

注意:像這樣的 Generics 要求所有子視圖的類型相同。 如果不是這樣,您可以將它們包裝在AnyView中,就像我在這里所做的那樣。 如果您對所有子視圖使用一致的類型(根視圖的類型不需要匹配),則不需要AnyView包裝器。

似乎這在 Xcode 13 中沒有得到解決。

在 Xcode 13 Big Sur 上測試,但不是在蒙特雷測試... 在此處輸入圖像描述

嘿嘿,所以我遇到的一個問題是我想要多個導航視圖層,我不確定這是否也是你的嘗試,但如果是的話: MacOS 不繼承導航視圖。 這意味着,您需要為您的 DetailView (或您的情況下的SecondView )提供它自己的NavigationView 因此,只需嵌入[...], destination: NavigationView { SecondView() }) [...]就可以了。

但是,小心。 對 iOS 目標執行相同操作將導致意外行為,因此,如果您同時針對兩者,請確保使用#if os(macOS)

但是,在進行設置視圖時,我建議您同時查看 Apple 提供的設置場景

您可以獲得全屏導航

 .navigationViewStyle(StackNavigationViewStyle())

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM