SwiftUI Скрыть панель TabView внутри представлений NavigationLink

У меня есть TabView и отдельные стеки NavigationView для каждого элемента Tab. Он работает хорошо, но когда я открываю любую ссылку 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 будет скрыта. Но это портит 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
        }
    }
}

С помощью этого решения единственный способ иметь разные NavigationTabBars для каждого элемента TabView - использовать вложенные NavigationViews. Может быть, есть способ правильно реализовать вложенные NavigationViews? (Насколько я знаю, в иерархии навигации должен быть только один NavigationView).

Как правильно скрыть панель TabView внутри представлений NavigationLink в SwiftUI?

7 ответов

Решение

Возможное обходное решение может быть основано на TabBarAccessorиз моего ответа о программном обнаружении панели вкладок или высоты TabView в SwiftUI

Вот необходимые изменения в удержании элемента вкладки 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.

Мне очень понравились решения, опубликованные выше, но мне не нравится тот факт, что TabBar не скрывается в соответствии с переходом представления. На практике, когда вы проводите пальцем влево для возврата при использовании tabBar.isHidden, результат неприемлем.

Я решил отказаться от встроенного SwiftUI TabView и написать свой собственный. Результат более красив в пользовательском интерфейсе:

Вот код, используемый для достижения этого результата:

Сначала определите несколько представлений:

       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. Вы можете, например, добавить значки уведомлений.

Благодаря другому 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")
    }
}

Вычислить navigationBarTitle а также navigationBarItems динамически:

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

Вышло обновление для iOS 16, теперь вы можете скрыть любую панель навигации. В этом случае:

      NavigationLink("Click") {
        Text("Next View")
            .toolbar(.hidden, for: .tabBar)
    }

Как насчет,

      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("Scan", systemImage: "square.and.pencil")
                }
                .tag(Tab.Scan)
            
            ValidateView()
                .tabItem {
                    Label("Validate", systemImage: "list.dash")
                }
                .tag(Tab.Validate)
            
            SettingsView()
                .tabItem {
                    Label("Settings", systemImage: "list.dash")
                }
                .tag(Tab.Settings)
        }
        .navigationBarTitle(Text(currentTab.rawValue), displayMode: .inline)
    }
}

Также вы можете создать очень похожий пользовательский navBar для представлений в TabView.

      struct CustomNavBarView<Content>: View where Content: View {
var title: String = ""
let content: Content

init(title: String, @ViewBuilder content: () -> Content) {
    self.title = title
    self.content = content()
}
var body: some View {
    content
        .safeAreaInset(edge: .top, content: {
            HStack{
                Spacer()
                Text(title)
                    .fontWeight(.semibold)
                Spacer()
            }
            .padding(.bottom, 10)
            .frame(height: 40)
            .frame(maxWidth: .infinity)
            .background(.ultraThinMaterial)
            .overlay {
                Divider()
                    .frame(maxHeight: .infinity, alignment: .bottom)
            }
        })
}
}



  CustomNavBarView(title: "Create ad"){
            ZStack{
               
                NavigationLink(destination: SetPinMapView(currentRegion: $vm.region, region: vm.region), isActive: $vm.showFullMap) {
                    Color.clear
                }
                
                Color("Background").ignoresSafeArea()
                
                content
                
            }
          
        }

Я также столкнулся с этой проблемой. Не хочу переписывать, но решение есть у меня на гитхабе. Я там все подробно написал https://github.com/BrotskyS/AdvancedNavigationWithTabView

PS: у меня нет репутации, чтобы писать комментарии. Решение Hikeland неплохое. Но вы не сохраняете состояние страницы. Если у вас есть ScrollView, он будет сбрасываться до нуля каждый раз, когда вы меняете вкладку

Другие вопросы по тегам