简体   繁体   English

SwiftUI 在 NavigationLink 视图中隐藏 TabView 栏

[英]SwiftUI Hide TabView bar inside NavigationLink views

I have a TabView and separate NavigationView stacks for every Tab item.我有一个 TabView 和每个 Tab 项的单独 NavigationView 堆栈。 It works well but when I open any NavigationLink the TabView bar is still displayed.它运行良好,但是当我打开任何 NavigationLink 时,仍然显示 TabView 栏。 I'd like it to disappear whenever I click on any NavigationLink.每当我单击任何 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)
        }
    }
}

I found a solution to put a TabView inside a NavigationView, so then after I click on a NavigationLink the TabView bar is hidden.我找到了将 TabView 放在 NavigationView 中的解决方案,因此在单击 NavigationLink 后,TabView 栏被隐藏。 But this messes up NavigationBarTitles for Tab items.但这会弄乱 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
        }
    }
}

With this solution the only way to have different NavigationTabBars per TabView item, is to use nested NavigationViews.使用此解决方案,使每个 TabView 项具有不同 NavigationTabBars 的唯一方法是使用嵌套 NavigationViews。 Maybe there is a way to implement nested NavigationViews correctly?也许有一种方法可以正确实现嵌套的 NavigationViews? (As far as I know there should be only one NavigationView in Navigation hierarchy). (据我所知,导航层次结构中应该只有一个 NavigationView)。

How can I hide TabView bar inside NavigationLink views correctly in SwiftUI?如何在 SwiftUI 中正确隐藏 NavigationLink 视图中的 TabView 栏?

The possible workaround solution can be based on TabBarAccessor from my answer on Programmatically detect Tab Bar or TabView height in SwiftUI可能的解决方法可以基于我在TabBarAccessor中以编程方式检测 Tab Bar 或 TabView 高度的回答中的 TabBarAccessor

Here is a required modification in tab item holding NavigationView .这是包含NavigationView的选项卡项中的必需修改。 Tested with Xcode 11.4 / iOS 13.4使用 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
        })
    }
}

Note: or course if FirstTabView should be reusable and can be instantiated standalone, then tabBar property inside should be made optional and handle ansbsent tabBar explicitly.注意:当然,如果FirstTabView应该是可重用的并且可以独立实例化,那么里面的tabBar属性应该是可选的,并显式处理 ansbsent tabBar。

Thanks to another Asperi's answer I was able to find a solution which does not break animations and looks natural .感谢另一个 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)
        }
    }
}
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")
    }
}

Compute navigationBarTitle and navigationBarItems dynamically:动态计算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("-")
        }
    }
}

I really enjoyed the solutions posted above, but I don't like the fact that the TabBar is not hiding according to the view transition.我真的很喜欢上面发布的解决方案,但我不喜欢 TabBar 没有根据视图转换隐藏的事实。 In practice, when you swipe left to navigate back when using tabBar.isHidden, the result is not acceptable.实际上,当您在使用 tabBar.isHidden 时向左滑动导航返回时,结果是不可接受的。

I decided to give up the native SwiftUI TabView and code my own.我决定放弃原生的 SwiftUI TabView 并自己编写代码。 The result is more beautiful in the UI:结果在 UI 中更漂亮:

iPhone 模拟器

Here is the code used to reach this result:这是用于达到此结果的代码:

First, define some views:首先,定义一些视图:

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

Then, create the TabBarView (which will be the root view used in your app):然后,创建 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
        }
    }
}

This solution also allows a lot of customization in the TabBar.此解决方案还允许在 TabBar 中进行大量自定义。 You can add some notifications badges, for example.例如,您可以添加一些通知徽章。

How about,怎么样,

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM