SwiftUI在子视图中隐藏TabBar

时间:2019-10-18 05:51:46

标签: swift xcode swiftui ios13

我正在使用SwiftUI,并且TabBar出现了一些问题。 我想在特定的子视图上隐藏TabBar。

尝试过

UITabBar.appearance().isHidden = true

它仅适用于TabView中的直接视图。但是,当我将其放置在子视图中时,它不起作用。

有人对此有解决方案吗?

谢谢。

15 个答案:

答案 0 :(得分:7)

iOS 14简单解决方案

安装Introspect SwiftPM:https://github.com/siteline/SwiftUI-Introspect

var body: some View {
    List {
        -your code here-
    }
    
    .navigationBarTitle("Title", displayMode: .inline)
    .introspectTabBarController { (UITabBarController) in
        UITabBarController.tabBar.isHidden = true
    }
}

注意:您必须在父视图中重新启用TabBar,否则它仍将被隐藏。

.introspectTabBarController { (UITabBarController) in
            UITabBarController.tabBar.isHidden = false
}

答案 1 :(得分:5)

它正在工作,只需要在主队列上调用更改

struct ShowTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            DispatchQueue.main.async {
                Tool.showTabBar()
            }
        }
    }
}

struct HiddenTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            DispatchQueue.main.async {
                Tool.hiddenTabBar()
            }
        }
    }
}

遍历窗口的allsubview以隐藏UITabBar。您可以将其编写为ViewModifier并在SwiftUI中使用它,也可以使用工具将其隐藏。这种方法对我有用。

    extension UIView {
        
        func allSubviews() -> [UIView] {
            var res = self.subviews
            for subview in self.subviews {
                let riz = subview.allSubviews()
                res.append(contentsOf: riz)
            }
            return res
        }
    }
    
    struct Tool {
        static func showTabBar() {
            UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
                if let view = v as? UITabBar {
                    view.isHidden = false
                }
            })
        }
        
        static func hiddenTabBar() {
            UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
                if let view = v as? UITabBar {
                    view.isHidden = true
                }
            })
        }
    }
    
    struct ShowTabBar: ViewModifier {
        func body(content: Content) -> some View {
            return content.padding(.zero).onAppear {
                Tool.showTabBar()
            }
        }
    }
    struct HiddenTabBar: ViewModifier {
        func body(content: Content) -> some View {
            return content.padding(.zero).onAppear {
                Tool.hiddenTabBar()
            }
        }
    }
    
    extension View {
        func showTabBar() -> some View {
            return self.modifier(ShowTabBar())
        }
        func hiddenTabBar() -> some View {
            return self.modifier(HiddenTabBar())
        }
    }

答案 2 :(得分:4)

将 NavigationView 添加为 root 而不是 TabView

 NavigationView{
    TabView(selection:$selectedIndex) {
     }
 }

如果您不想在 TabBar 页面中使用 NavigationView,只需将其隐藏即可。

.navigationBarHidden(true)

参考示例项目 - https://github.com/TreatTrick/Hide-TabBar-In-SwiftUI

答案 3 :(得分:2)

我正在使用的基本思想是将ObservableObject和ZStack结合起来。我已经将TabView与条件子视图演示一起放入ZStack。 It's look like。 浏览github repo

答案 4 :(得分:2)

实际上可以通过使用这个方便的小框架为TabView获取底层的UITabbarController:

https://github.com/siteline/SwiftUI-Introspect

该解决方案以MVVM模式为例,以编程方式控制Tabbar的可见性,并能够使用NSNotifications在代码中的任何位置显示,隐藏,启用,禁用表单

SwiftUI视图:像这样设置tabview

struct MainTabView: View {

var viewModel: MainTabViewModel

var body: some View {

    TabView() {

        Text("View1")
        .tabItem {
            Text("View1")
        }

        Text("View2")
        .tabItem {
            Text("View2")
        }

    }

    .introspectTabBarController { tabBarController in
        // customize here the UITabBarViewController if you like
        self.viewModel.tabBarController = tabBarController
    }

}
}

然后使用ViewModel

final class MainTabViewModel: ObservableObject {

var tabBarController: UITabBarController?

init() {
    startListeningNotifications()
}

func startListeningNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(showTabbarView), name: "showBottomTabbar", object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(hideTabbarView), name: "hideBottomTabbar", object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(enableTabbarTouch), name: "enableTouchTabbar", object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(disableTabbarTouch), name: "disableTouchTabbar", object: nil)
}

@objc func showTabbarView() {
    self.tabBarController?.tabBar.isHidden = false
}

@objc func hideTabbarView() {
    self.tabBarController?.tabBar.isHidden = true
}

@objc func disableTabbarTouch() {
    self.tabBarController?.tabBar.isUserInteractionEnabled = false
}

@objc func enableTabbarTouch() {
    self.tabBarController?.tabBar.isUserInteractionEnabled = true
}

deinit {
    NotificationCenter.default.removeObserver(self)
}


}

最后要控制标签栏,只需在您喜欢的地方使用这些功能(将以本示例的模式出现在viewmodel中)

public func showTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .showBottomTabbar, object: nil)
    }
}

public func hideTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .hideBottomTabbar, object: nil)
    }
}

public func enableTouchTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .enableTouchTabbar, object: nil)
    }
}

public func disableTouchTabbar() {
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: .disableTouchTabbar, object: nil)
    }
}

答案 5 :(得分:1)

这里没有隐藏TabView的方法,所以我必须在ZStack中这样添加TabView:

var body: some View {
    ZStack {
        TabView {
            TabBar1().environmentObject(self.userData)
                .tabItem {
                    Image(systemName: "1.square.fill")
                    Text("First")
            }
            TabBar2()
                .tabItem {
                    Image(systemName: "2.square.fill")
                    Text("Second")
            }
        }

        if self.userData.showFullScreen {
            FullScreen().environmentObject(self.userData)

        }
    }
}

UserData:

  final class UserData: ObservableObject {
    @Published var showFullScreen = false
}

TabBar1:

struct TabBar1: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        Text("TabBar 1")
            .edgesIgnoringSafeArea(.all)
            .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            .background(Color.green)
            .onTapGesture {
                self.userData.showFullScreen.toggle()
        }
    }
}

全屏:

struct FullScreen: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        Text("FullScreen")
            .edgesIgnoringSafeArea(.all)
            .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            .background(Color.red)
            .onTapGesture {
                self.userData.showFullScreen.toggle()
        }
    }
}

检查Github上的完整代码

还有其他一些方法,但这取决于视图的结构

答案 6 :(得分:1)

正确答案是优雅,使用原生 SwiftUI 方法,并允许以任何方式全屏显示任何类型的视图,在任何类型的视图之上是下面。 所有其他答案都使用 3rd-party 包,以不可恢复的方式进入 UIKit 或不优雅。

首先,您需要将要隐藏的视图声明为允许全屏覆盖的视图。一般的想法是,全屏但您想隐藏的视图应该有一个首选项键,用于存储要在其上显示的视图:

/// A preference that allows you to show full screen overlays on top of any top-level full screen view.
///
/// This is particularly useful when trying to present views over a `TabView` or a `NavigationView` which block placing views over
/// their respective safe zones (like the tab bar or the navigation bar).
struct FullScreenCoverPreferenceKey: PreferenceKey {
    typealias Value = [OverlayView]
    static var defaultValue: [OverlayView] = []
    
    static func reduce(value: inout [OverlayView], nextValue: () -> [OverlayView]) {
        value.append(contentsOf: nextValue())
    }
    
    /// The underlying overlay view behind the preference key.
    struct OverlayView: View, Identifiable {
        let id = UUID()
        
        var content: AnyView
        
        init<Content: View>(@ViewBuilder content: () -> Content) {
            self.content = AnyView(content())
        }
        
        var body: some View {
            content
        }
    }
}

FullScreenCoverPreferenceKey.value 将包含要显示在全屏视图顶部的视图数组(例如 TabView)。

然后,使用以下方法扩展 View 协议,以便将首选项键附加到视图:

extension View {
    /// Declares that this view is capable of receiving full screen overlays.
    ///
    /// - Important
    /// This modifier should only be used on views that are taking up the entire screen.
    func allowFullScreenOverlays() -> some View {
        self
            .overlayPreferenceValue(FullScreenCoverPreferenceKey.self) { overlayViewsArray in
                ZStack {
                    ForEach(overlayViewsArray) { overlayView in
                        overlayView
                    }
                }
            }
    }
}

overlayPreferenceValue 的返回视图呈现为由首选项键的 ZStack 数组组成的 [OverlayView] 允许我们将多个叠加层堆叠在一起。

接下来,创建任何类型的叠加层并将其附加到 TabView 的任何子视图:

ZStack {
    Color.white.ignoresSafeArea() //background if needed

    TabView {
        NavigationView {
            ZStack {
                NavigationLink(
                    destination:
                        ZStack { Color.orange }
                            .transformPreference(FullScreenCoverPreferenceKey.self) {
                                $0.append(FullScreenCoverPreferenceKey.OverlayView {
                                    //The overlay
                                    ZStack {
                                        Color.green
                                        Text("I am full screen!")
                                    }
                                })
                            },
                    label: {
                        Text("Press me to show full screen!")
                    })
                }
            }
            .tabItem {
                Text("Tab")
            }
    }
}
.allowFullScreenOverlays()

最终视图将如下所示,Tab 被完全覆盖:

enter image description here

答案 7 :(得分:0)

只需使用UIKit的UINavigationController。像这样:

让主机= UINavigationController(rootViewController: UIHostingController(rootView:HLHome()))

答案 8 :(得分:0)

这似乎对我有用。

someSubView{
//your code here
}.onAppear(perform: {
                UITabBar.appearance().isHidden = true
            })
)

答案 9 :(得分:0)

iOS 14

安装Introspect SwiftPM:https://github.com/siteline/SwiftUI-Introspect

struct SomeView: View{
    
    @State var uiTabarController: UITabBarController?
    
    var body: some View {
        List {
            -your code here-
        }
        
        .navigationBarTitle("Title", displayMode: .inline)
        .introspectTabBarController { (UITabBarController) in
            UITabBarController.tabBar.isHidden = true
            uiTabarController = UITabBarController
        }.onDisappear{
            uiTabarController?.tabBar.isHidden = false
        }
    }
}

uiTabarController中的这段代码中,我们引用了UITabarController。当我们返回时,我们再次启用了Tabar。因此,这就是为什么需要这样做的原因。

答案 10 :(得分:0)

我遇到了同样的问题,最终使用了:

.opacity(hideTabBar == true ? 0 : 1)

其中 hideTabBar 是我传递给需要隐藏 TabBar() 的视图的 Bool。

所以基本上你应该尝试这样的事情:

 TabView(selection: your desired tab).opacity(hideTabBar == true ? 0 : 1)

答案 11 :(得分:0)

像这样增加 TabView 的框架大小:

.frame(width: UIScreen.main.bounds.width, height: showTabbar ? UIScreen.main.bounds.height : UIScreen.main.bounds.height + 100.00)

答案 12 :(得分:0)

不理想和笨拙,但对我来说效果很好的最简单的方法是隐藏外部导航视图的导航栏,然后在每个 TabView 的视图中添加另一个导航视图。到目前为止运行良好:

struct LaunchView: View {
    var body: some View {
        NavigationView {
            TabView {
                ViewA()
                    .tabItem {
                        Label("TabA", systemImage: "some.image")
                    }
                ViewB()
                    .tabItem {
                        Label("TabB", systemImage: "some.image")
                    }
                ViewC()
                    .tabItem {
                        Label("TabC", systemImage: "some.image")
                    }
            }
            .navigationBarHidden(true)
        }
    }
}

struct ViewA: View {
        
    var body: some View {
        NavigationView {
            // Content
            .navigationTitle("Settings")
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

通过这种方式,您可以在每个单独的视图中设置标题以及 .toolBarItem。

答案 13 :(得分:0)

iOS 15 解决方案

此解决方案运行良好,除了 SwiftUI.TabView 中的视图修饰符。 由于我的 TabView 在符合 App 的结构中,看起来连接的场景中仍然没有任何 UITabBar 子视图。

使用下面的代码,您只需在 SwiftUI.View 中使用 showTabBar()hiddenTabBar()

extension UIApplication {
    var key: UIWindow? {
        self.connectedScenes
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?
            .windows
            .filter({$0.isKeyWindow})
            .first
    }
}


extension UIView {
    func allSubviews() -> [UIView] {
        var subs = self.subviews
        for subview in self.subviews {
            let rec = subview.allSubviews()
            subs.append(contentsOf: rec)
        }
        return subs
    }
}
    

struct TabBarModifier {
    static func showTabBar() {
        UIApplication.shared.key?.allSubviews().forEach({ subView in
            if let view = subView as? UITabBar {
                view.isHidden = false
            }
        })
    }
    
    static func hideTabBar() {
        UIApplication.shared.key?.allSubviews().forEach({ subView in
            if let view = subView as? UITabBar {
                view.isHidden = true
            }
        })
    }
}

struct ShowTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            TabBarModifier.showTabBar()
        }
    }
}
struct HiddenTabBar: ViewModifier {
    func body(content: Content) -> some View {
        return content.padding(.zero).onAppear {
            TabBarModifier.hideTabBar()
        }
    }
}

extension View {
    
    func showTabBar() -> some View {
        return self.modifier(ShowTabBar())
    }

    func hiddenTabBar() -> some View {
        return self.modifier(HiddenTabBar())
    }
}

答案 14 :(得分:-2)

此解决方案实际上可在iOS 14上运行

.onAppear(perform: {
                UITabBar.appearance().isHidden = true
            })