SwiftUI 在 NavigationLink 视图中隐藏 TabView 栏

Posted

技术标签:

【中文标题】SwiftUI 在 NavigationLink 视图中隐藏 TabView 栏【英文标题】:SwiftUI Hide TabView bar inside NavigationLink views 【发布时间】:2020-09-10 05:36:37 【问题描述】:

我有一个 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)
        
    

我找到了将 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
        
    

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

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

【问题讨论】:

***.com/questions/57304876/… ? @rbaldwin 这个解决方案弄乱了 NavigationBar 的标题。我想让每个标签项都有不同的 NavigationBar 标题。在 NavigationView 中使用 TabView 我只能设置一次 NavigationBar 标题。 【参考方案1】:

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

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

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

首先,定义一些视图:

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))
    

然后,创建 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
        
    

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

【讨论】:

好主意,谢谢,真的帮了我大忙! 这很有帮助。我稍微调整了一下并将 zstack 包装在 NavigationView 中,这样我就可以直接从 TabBarView 推送到全屏视图,因此在导航方面,thirdview 实际上是 tabbarview 的兄弟(就像在 UIKIt 中一样)。有了这个,我也不需要检查何时或何时不在 zstack 中添加 tabbarview。【参考方案2】:

可能的解决方案可以基于我对Programmatically detect Tab Bar or TabView height in SwiftUI的回答中的TabBarAccessor

这里是对持有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
        )
    

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

【讨论】:

它有效,但并不完美。这样,当我从子视图返回时,标签栏仅在整个过渡动画完成后才会出现。但是如果我没有找到任何其他方法,我会使用它。 我没有找到更好的解决方案,所以我接受你的回答。我只是不明白为什么没有更好的方法来做到这一点。感觉我的应用的层次结构很标准。【参考方案3】:

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

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)
        
    

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")
    

动态计算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("-")
        
    

【讨论】:

我打算使用您将 TabView 放入 NavigationView 的方法。你在 iOS14 的 SwiftUI 2 中遇到过这种方法的问题吗? @Gary Nope,对我来说很好用。您始终可以自己测试它 - 上面的代码已准备好复制粘贴。如果您的目标是 iOS14+,您可能只想将 .navigationBarTitle 更改为 .navigationTitle 我确实在模拟器中对其进行了实际测试,它确实有效:) 我仍然问的原因是因为 hackingwithswift 等其他一些网站说将 TabView 放在 NavigationView 中是一个坏主意(例如 @ 987654322@),所以我很好奇随着你的项目变得更大更复杂,你是否遇到了这种方法的任何错误。听起来你对它没有任何问题,所以我也会采用这种方法。 此解决方案不适合在 iOS 15.2 中使用。导航栏行为异常。【参考方案4】:

怎么样,

struct TabSelectionView: View 
    @State private var currentTab: Tab = .Scan
    
    private enum Tab: String 
        case Scan, Validate, Settings
    
    
    var body: some View 
        TabView(selection: $currentTab)
            
            ScanView()
                .tabItem 
                    Label(Tab.Scan.rawValue, systemImage: "square.and.pencil")
                
                .tag(Tab.Scan)
            
            ValidateView()
                .tabItem 
                    Label(Tab.Validate.rawValue, systemImage: "list.dash")
                
                .tag(Tab.Validate)
            
            SettingsView()
                .tabItem 
                    Label(Tab.Settings.rawValue, systemImage: "list.dash")
                
                .tag(Tab.Settings)
        
        .navigationBarTitle(Text(currentTab.rawValue), displayMode: .inline)
    

【讨论】:

以上是关于SwiftUI 在 NavigationLink 视图中隐藏 TabView 栏的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 在 NavigationLink 中更改后退按钮的颜色

SwiftUI:NavigationLink 奇怪的行为

如何在 SwiftUI 中为 NavigationLink 创建工厂方法?

SwiftUI 在 NavigationLink 视图中隐藏 TabView 栏

如何在 SwiftUI 列表中设置 NavigationLink

SwiftUI 上的 NavigationLink 两次推送视图