SwiftUI 在 NavigationLink 视图中隐藏 TabView 栏

paw*_*222 7 ios swift swiftui swiftui-tabview swiftui-navigationview

我有一个 TabView 和每个 Tab 项的单独 NavigationView 堆栈。它运行良好,但是当我打开任何 NavigationLink 时,仍会显示 TabView 栏。我希望它在我点击任何 NavigationLink 时消失。

struct MainView: View {
    @State private var tabSelection = 0

    var body: some View {
        TabView(selection: $tabSelection) {
            FirstView()
                .tabItem {
                    Text("1")
                }
                .tag(0)
            SecondView()
                .tabItem {
                    Text("2")
                }
                .tag(1)
        }
    }
}

struct FirstView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: FirstChildView()) { // How can I open FirstViewChild with the TabView bar hidden?
                Text("Go to...")
            }
            .navigationBarTitle("FirstTitle", displayMode: .inline)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我找到了将 TabView 放入 NavigationView 的解决方案,因此在单击 NavigationLink 后,TabView 栏被隐藏。但这会弄乱 Tab 项的 NavigationBarTitles。

struct MainView: View {
    @State private var tabSelection = 0

    var body: some View {
        NavigationView {
            TabView(selection: $tabSelection) {
                ...
            }
        }
    }
}

struct FirstView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: FirstChildView()) {
                Text("Go to...")
            }
            .navigationBarTitle("FirstTitle", displayMode: .inline) // This will not work now
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用此解决方案,为每个 TabView 项设置不同的 NavigationTabBar 的唯一方法是使用嵌套的 NavigationView。也许有一种方法可以正确实现嵌套的 NavigationViews?(据我所知,导航层次结构中应该只有一个 NavigationView)。

如何在 SwiftUI 中正确隐藏 NavigationLink 视图中的 TabView 栏?

Hik*_*and 16

我真的很喜欢上面发布的解决方案,但我不喜欢 TabBar 没有根据视图转换隐藏这一事实。实践中,当使用tabBar.isHidden时向左滑动导航返回时,结果是不可接受的。

我决定放弃原生 SwiftUI TabView 并编写自己的代码。UI 中的结果更加美观:

iPhone模拟器

以下是用于达到此结果的代码:

首先定义一些视图:

struct FirstView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("First View")
                    .font(.headline)
            }
            .navigationTitle("First title")
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
            .background(Color.yellow)
        }
    }
}

struct SecondView: View {
    var body: some View {
        VStack {
            NavigationLink(destination: ThirdView()) {
                Text("Second View, tap to navigate")
                    .font(.headline)
            }
        }
        .navigationTitle("Second title")
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
        .background(Color.orange)
    }
}

struct ThirdView: View {
    var body: some View {
        VStack {
            Text("Third View with tabBar hidden")
                .font(.headline)
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
        .background(Color.red.edgesIgnoringSafeArea(.bottom))
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,创建 TabBarView (这将是您的应用程序中使用的根视图):

struct TabBarView: View {
    enum Tab: Int {
        case first, second
    }
    
    @State private var selectedTab = Tab.first
    
    var body: some View {
        VStack(spacing: 0) {
            ZStack {
                if selectedTab == .first {
                    FirstView()
                }
                else if selectedTab == .second {
                    NavigationView {
                        VStack(spacing: 0) {
                            SecondView()
                            tabBarView
                        }
                    }
                }
            }
            .animation(nil)
            
            if selectedTab != .second {
                tabBarView
            }
        }
    }
    
    var tabBarView: some View {
        VStack(spacing: 0) {
            Divider()
            
            HStack(spacing: 20) {
                tabBarItem(.first, title: "First", icon: "hare", selectedIcon: "hare.fill")
                tabBarItem(.second, title: "Second", icon: "tortoise", selectedIcon: "tortoise.fill")
            }
            .padding(.top, 8)
        }
        .frame(height: 50)
        .background(Color.white.edgesIgnoringSafeArea(.all))
    }
    
    func tabBarItem(_ tab: Tab, title: String, icon: String, selectedIcon: String) -> some View {
        ZStack(alignment: .topTrailing) {
            VStack(spacing: 3) {
                VStack {
                    Image(systemName: (selectedTab == tab ? selectedIcon : icon))
                        .font(.system(size: 24))
                        .foregroundColor(selectedTab == tab ? .primary : .black)
                }
                .frame(width: 55, height: 28)
                
                Text(title)
                    .font(.system(size: 11))
                    .foregroundColor(selectedTab == tab ? .primary : .black)
            }
        }
        .frame(width: 65, height: 42)
        .onTapGesture {
            selectedTab = tab
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案还允许在 TabBar 中进行大量自定义。例如,您可以添加一些通知徽章。


Asp*_*eri 6

可能的解决方法可以基于TabBarAccessor我对以编程方式检测 SwiftUI 中的 Tab Bar 或 TabView 高度的回答

这是选项卡项目保持中的必需修改NavigationView。使用 Xcode 11.4 / iOS 13.4 测试

演示

struct FirstTabView: View {
    @State private var tabBar: UITabBar! = nil

    var body: some View {
        NavigationView {
            NavigationLink(destination:
                FirstChildView()
                    .onAppear { self.tabBar.isHidden = true }     // !!
                    .onDisappear { self.tabBar.isHidden = false } // !!
            ) {
                Text("Go to...")
            }
            .navigationBarTitle("FirstTitle", displayMode: .inline)
        }
        .background(TabBarAccessor { tabbar in   // << here !!
            self.tabBar = tabbar
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:或者当然如果FirstTabView应该是可重用的并且可以独立实例化,那么tabBar里面的属性应该是可选的并显式处理 ansbsent tabBar。

  • 它有效,但并不完美。这样,当我从子视图返回时,选项卡栏仅在整个过渡动画完成后才会出现。但如果我找不到其他方法,我会使用它。 (4认同)
  • 我没有找到更好的解决方案,所以我接受你的答案。我只是不明白为什么没有更好的方法来做到这一点。感觉我的应用程序的层次结构非常标准。 (3认同)

paw*_*222 5

感谢另一个 Asperi 的回答,我能够找到一个不会破坏动画并且看起来很自然的解决方案。

struct ContentView: View {
    @State private var tabSelection = 1

    var body: some View {
        NavigationView {
            TabView(selection: $tabSelection) {
                FirstView()
                    .tabItem {
                        Text("1")
                    }
                    .tag(1)
                SecondView()
                    .tabItem {
                        Text("2")
                    }
                    .tag(2)
            }
            // global, for all child views
            .navigationBarTitle(Text(navigationBarTitle), displayMode: .inline)
            .navigationBarHidden(navigationBarHidden)
            .navigationBarItems(leading: navigationBarLeadingItems, trailing: navigationBarTrailingItems)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
struct FirstView: View {
    var body: some View {
        NavigationLink(destination: Text("Some detail link")) {
            Text("Go to...")
        }
    }
}

struct SecondView: View {
    var body: some View {
        Text("We are in the SecondView")
    }
}
Run Code Online (Sandbox Code Playgroud)

计算navigationBarTitlenavigationBarItems动态:

private extension ContentView {
    var navigationBarTitle: String {
        tabSelection == 1 ? "FirstView" : "SecondView"
    }
    
    var navigationBarHidden: Bool {
        tabSelection == 3
    }

    @ViewBuilder
    var navigationBarLeadingItems: some View {
        if tabSelection == 1 {
            Text("+")
        }
    }

    @ViewBuilder
    var navigationBarTrailingItems: some View {
        if tabSelection == 1 {
            Text("-")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 此解决方案不适合在 iOS 15.2 中使用。导航栏的行为异常。 (2认同)

Xen*_*ion 5

iOS 16 有更新,您现在可以隐藏任何导航栏。在这种情况下:

NavigationLink("Click") {
        Text("Next View")
            .toolbar(.hidden, for: .tabBar)
    }
Run Code Online (Sandbox Code Playgroud)