在UITabBar之上显示视图

15
类似于Spotify或Apple Music应用程序在播放音乐时所做的,它会将自定义视图放置在UITabBar的顶部: enter image description here

我尝试过的解决方案:

  1. 将UITabBarController放在一个ViewController中,并使用最大尺寸的Container View,在底部布局指南的上方49pt放置自定义视图: enter image description here 问题: UITabBarController嵌入的视图控制器中底部约束的任何内容都不会显示,因为它们被隐藏在自定义布局后面。我尝试了在UITabBarController中覆盖 size forChildContentContainer、更新底部布局指南等方法,但都没有成功。 我需要调整UITabBarController容器视图的框架大小。

  2. 再次尝试#1,但通过增大UITabBar的尺寸来解决内容隐藏在其后的问题,然后使用每个TabBarItem上的ImageInset将其下移,并在UITabBar的顶部添加自定义视图。没有非常好地工作。有时我想隐藏我的自定义视图。

  3. 将UITabBarController作为根控制器,每个子控制器都是一个带有Container View和我的自定义视图的ViewController: enter image description here

但现在我有多个自定义视图实例漂浮在周围。如果我想要更改标签,必须将其更改到所有视图中。或者隐藏等。

  • 重写UITabBarController的UITabBar属性,并返回我的自定义UITabBar(用xib填充),其中包含UITabBar +我的自定义视图。问题:可能是所有尝试中最令人沮丧的。如果您使用class MyCustomTabBar:UITabBar {} 的实例覆盖该属性,则不会显示任何选项卡!是的,我将myCustomTabBar的委托设置为self

  • 倾向于#3,但正在寻找更好的解决方案。


    #3 明显是一个权宜之计,必须有更好的解决方案。 - sweta.me
    @Azizi Javed,你可以将视图添加到UINavigationController上。 - aircraft
    @aircraft可以发布一个你所指的示例吗?因为告诉孩子有限的视图控制器的问题仍然存在。 - azizj
    @sweta.me 我添加了第四个解决方案,但那个更糟糕了:\ - azizj
    7个回答

    14

    如果您通过子类化UITabBarController并编程方式添加视图,则实际上非常容易。使用此技术会自动支持选项卡栏的旋转和大小更改,无论您使用哪个版本。

    class CustomTabBarController: UITabBarController {
      override func viewDidLoad() {
        super.viewDidLoad()
    
        //...do some of your custom setup work
        // add a container view above the tabBar
        let containerView = UIView()
        containerView.backgroundColor = .red
        view.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        containerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
    
        // anchor your view right above the tabBar
        containerView.bottomAnchor.constraint(equalTo: tabBar.topAnchor).isActive = true
    
        containerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
      }
    }
    

    4
    这并不是将视图控制器嵌入选项卡控制器中,这正是重点所在。 - Jake
    1
    为了插入视图控制器,您可以在所有视图控制器的基类中执行additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 42, right: 0)。 - WingJammer
    additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 42, right: 0) 这在 iOS 14 上不起作用。 - WingJammer

    9

    从iOS 11开始,这变得更容易了。当您添加视图时,可以执行以下操作:

    viewControllers?.forEach {
       $0.additionalSafeAreaInsets = UIEdgeInsets(
          top: 0, 
          left: 0,
          bottom: yourView.height,
          right: 0
       )
    }
    

    2
    我认为你的回答真的没有得到足够的赞赏。如果可以的话,我会毫不犹豫地给它点赞 :) - Jan Nash
    这确实是这里最好的答案。使用此函数为所有视图控制器设置安全区域插图,然后使用keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow })获取所有视图中最顶层的视图,并将您的视图添加到keyWindow.addSubview(yourView)。对我来说效果很好。 - JustWorkAlready
    1
    你真是个救命恩人! - FledgeXu

    9

    我明白了!

    enter image description here

    本质上,我增加了原始UITabBar的大小以容纳自定义视图(并缩小了视图控制器的框架),然后在其上方添加了一个重复的UITabBar +自定义视图。

    这是我必须要做的关键。我上传了一个可运行的示例,可以在此存储库中找到:

    class TabBarViewController: UITabBarController {
    
        var currentlyPlaying: CurrentlyPlayingView!
        static let maxHeight = 100
        static let minHeight = 49
        static var tabbarHeight = maxHeight
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            currentlyPlaying = CurrentlyPlayingView(copyFrom: tabBar)
            currentlyPlaying.tabBar.delegate = self
    
            view.addSubview(currentlyPlaying)
            tabBar.isHidden = true
        }
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            currentlyPlaying.tabBar.items = tabBar.items
            currentlyPlaying.tabBar.selectedItem = tabBar.selectedItem
        }
        func hideCurrentlyPlaying() {
            TabBarViewController.tabbarHeight = TabBarViewController.minHeight
            UIView.animate(withDuration: 0.5, animations: {
                self.currentlyPlaying.hideCustomView()
                self.updateSelectedViewControllerLayout()
            })
        }
        func updateSelectedViewControllerLayout() {
            tabBar.sizeToFit()
            tabBar.sizeToFit()
            currentlyPlaying.sizeToFit()
            view.setNeedsLayout()
            view.layoutIfNeeded()
            viewControllers?[self.selectedIndex].view.setNeedsLayout()
            viewControllers?[self.selectedIndex].view.layoutIfNeeded()
        }
    }
    
    extension UITabBar {
    
        open override func sizeThatFits(_ size: CGSize) -> CGSize {
            var sizeThatFits = super.sizeThatFits(size)
            sizeThatFits.height = CGFloat(TabBarViewController.tabbarHeight)
            return sizeThatFits
        }
    }
    

    3
    不错的解决方案,但是当您旋转设备时它会出问题。 - Chris Hinkle
    1
    @Aziz似乎现在无法工作,选项卡未显示,github代码也无法使用,在iPhone X模拟器中进行了检查。 - Meghs Dhameliya
    @aziz 看起来在调试时表现很奇怪,但如果应用程序在没有 Xcode 的情况下启动,则可以正常工作。 - Abdul Karim

    2

    将它放在包装的视图控制器中是个好主意,但这只会导致额外的开销(内存中加载更多视图控制器),以及当你后来想要更改代码时会出现问题。如果你想让栏始终显示在你的UITabBarController上,那么你应该在那里添加它。

    你应该对UITabBarController进行子类化,并从nib中加载自定义栏。在那里,你将可以访问tabbar(以便你可以在其上正确地放置你的栏),并且你只会加载它一次(这解决了你将面临的每个选项卡上都有不同栏的问题)。

    至于你的视图没有响应自定义栏的大小,我不知道你怎么做到的,但我的最佳建议是使用公共变量和通知,在你的各个选项卡中监听它们。
    然后你可以用它来改变底部约束。


    我可以在UITabBarController中从nib中加载自定义视图,但是如何显示它呢?有些方法可以让UITabBarController显示它(这将是理想的),或者让每个子视图像#3中所示的那样显示它? - azizj
    子类化UITabBarController并从nib中加载它。然后,您可以将其添加到视图中,并在代码中将其约束到选项卡栏的顶部。您可以使用固定的49像素(选项卡栏的高度)来约束它,也可以使用选项卡栏的位置来约束它。 - vrwim
    啊,对了。但是子视图控制器扩展到自定义视图下面的问题...我可以在TabBarController和它的子视图之间放置一个容器视图来限制大小,类似于#3,但在TabBarController中显示自定义视图。 - azizj
    很抱歉,您只能限制选项卡停留在特定的高度。另一个选择是创建自己的视图控制器,其中包含选项卡栏、自定义视图和用于内容的容器视图。然后,您可以自己处理选项卡切换。不幸的是,这需要更多的工作,但如果您不想让每个选项卡都在视图之前停止,那么这是正确的方式。 - vrwim
    发布了一个包含你的答案的解决方案。 :) 让我知道你的想法。 - azizj

    1
    除了使用UITabBar或容器vc玩耍之外,您还可以考虑像以下帖子中那样将视图添加到主窗口中的应用委托中:

    在所有ViewControllers上方浮动的视图

    由于您的视图沿着选项卡栏四周,因此在应用程序委托中制作它完全可以。
    您始终可以通过将其设置为应用程序委托的属性来从应用程序委托单例访问浮动视图。然后很容易在代码的任何地方控制其可见性。
    更改浮动视图和超级视图窗口之间的约束常量可以调整视图的位置,从而优美地响应方向更改。
    另一种(类似)方法是使浮动视图成为像uid按钮一样的另一个窗口。

    1
    除非我理解有误,您可以从UITabBarController类创建自定义视图。然后,您可以将其插入到tabBar对象之上并对其进行约束,该对象是与控制器相关联的tabBar。因此,从UITabBarController类中创建您的自定义视图。
    class CustomTabBarController: UITabBarController {
        var customView: UIView = {
                let bar = UIView()
                bar.backgroundColor = .white
                bar.translatesAutoresizingMaskIntoConstraints = false
                return bar
            }()
    

    在viewDidLoad()方法中,将你的自定义视图添加到UITabBarController的视图对象中,并将其放置在tabBar对象之
    override func viewDidLoad() {
            super.viewDidLoad()
            
            ...
    
            self.view.insertSubview(customView, aboveSubview: tabBar)
    

    当您的自定义视图作为子视图添加后,添加约束以使其正确定位。这应该在viewDidLoad()中完成,但仅在插入视图后才能完成。

    self.view.addConstraints([
                NSLayoutConstraint(item: customView, attribute: .leading, relatedBy: .equal, toItem: tabBar, attribute: .leading, multiplier: 1, constant: 0),
                NSLayoutConstraint(item: customView, attribute: .trailing, relatedBy: .equal, toItem: tabBar, attribute: .trailing, multiplier: 1, constant: 0),
                NSLayoutConstraint(item: customView, attribute: .top, relatedBy: .equal, toItem: tabBar, attribute: .top, multiplier: 1, constant: -50),
                NSLayoutConstraint(item: customView, attribute: .bottom, relatedBy: .equal, toItem: tabBar, attribute: .top, multiplier: 1, constant: 0)
                ])
    

    有许多创造性的方法可以设置约束以实现您想要的效果,但是上述约束应该将一个视图附加在 tabBar 上方,并具有高度为 50。

    我发现了这段代码只有一个问题。当你尝试将新的viewController推入hidesBottomBarWhenPushed设置为true的navigationController时,你会发现当你返回到上一个viewController时,视图会消失。 - ShadeToD
    有趣的是,我从未使用或修改过此属性,因此我从未遇到过这个问题。当视图被隐藏时,可能会影响与视图相关联的所有约束(这应该得到研究,我只是猜测)。如果是这种情况,那么上面提到的约束将不得不在选项卡栏重新出现(从隐藏到可见)时重新应用。 - UKnow

    0
    1. 用选项卡栏的高度创建视图框架,并将其置于顶部。2. 设置 tabBar 隐藏为 true。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接