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")
动态计算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("-")
【讨论】:
我打算使用您将 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 视图中隐藏 TabView 栏