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, он будет сбрасываться до нуля каждый раз, когда вы меняете вкладку