azizj
azizj

Reputation: 3787

View on Top of UITabBar

Similar to what the Spotify or Apple Music app does when a song is playing, it places a custom view on top of the UITabBar: enter image description here

Solutions I've tried:

  1. UITabBarController in a ViewController with a max-sized Container View, and the custom view on top of the Container View49pt above the Bottom Layout Guide: enter image description here Problem: Any content in ViewControllers embedded in the UITabBarController constrained to the bottom don't show because they're hidden behind the custom layout. I've tried overriding size forChildContentContainer in UITabBarController, tried updating the bottom layout guide, Nothing. I need to resize the frame of container view of the UITabBarController.

  2. Tried #1 again, but tried solving the problem of content hiding behind it by increasing the size of UITabBar, and then using ImageInset on every TabBarItem to bring it down, and adding my custom view on top of the UITabBar. Hasn't worked really well. There are going to be times when I want to hide my custom view.

  3. UITabBarController as root, with each children being a ViewController with a Container View + my custom view: enter image description here But now I have multiple instances of my custom view floating around. If I want to change a label on it, have to change it to all views. Or hide, etc.

  4. Override the UITabBar property of UITabBarController and return my custom UITabBar (inflated it with a xib) that has a UITabBar + my custom view. Problem: Probably the most frustrating attempt of all. If you override that property with an instance of class MyCustomTabBar : UITabBar {}, no tab shows up! And yes, I set the delegate of myCustomTabBar to self.

Leaning towards #3, but looking for a better solution.

Upvotes: 15

Views: 18021

Answers (7)

UKnow
UKnow

Reputation: 21

Unless I've misunderstood, you could create a custom view from your UITabBarController class. You can then insert it above and constrain it to the tabBar object, which is the tabBar associated with the controller.

So from your UITabBarController class, create your custom view

class CustomTabBarController: UITabBarController {
    var customView: UIView = {
            let bar = UIView()
            bar.backgroundColor = .white
            bar.translatesAutoresizingMaskIntoConstraints = false
            return bar
        }()

In viewDidLoad() add your custom view to the UITabBarController's view object and place it above the tabBar object

override func viewDidLoad() {
        super.viewDidLoad()
        
        ...

        self.view.insertSubview(customView, aboveSubview: tabBar)

Then after your custom view is added as a subView, add constraints so it's positioned correctly. This should also be done in viewDidLoad() but only after your view is inserted.

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)
            ])

There's a bunch of creative ways you can setup constraints to do what you want, but the constraints above should attach a view above your tabBar with a height of 50.

Upvotes: 1

Tiago Almeida
Tiago Almeida

Reputation: 14237

Since iOS 11 this became a little easier. When you add your view, you can do the following:

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

Upvotes: 9

Ning
Ning

Reputation: 158

  1. Make the view's frame with the height of tab bar and brings it to top, 2. set tabBar hidden is true.

Upvotes: 0

noobular
noobular

Reputation: 3287

This is actually very easy if you subclass UITabBarController and add your view programmatically. Using this technique automatically supports rotation and size changes of the tab bar, regardless of which version you are on.

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
  }
}

Upvotes: 14

user8458999
user8458999

Reputation:

Besides playing with UITabBar or container vc, you could also consider adding the view in the App Delegate to the main window like in following post:

View floating above all ViewControllers

Since your view is all around along with the Tab bar, it is totally ok to make it in the App Delegate.

You can always access the Floating view from App Delegate Singleton by making it a property of the App Delegate. It is easy then to control its visibility in anywhere of your code.

Changing constant of the Constraints between the Floating view and super view window can adjust the position of the view, thus handsomely respond to orientation changes.

Another(similar) approach is to make the floating view another window like the uid button.

Upvotes: 1

azizj
azizj

Reputation: 3787

I got it!

enter image description here

In essence, I increased the size of the original UITabBar to accomodate a custom view (and to shrink the frame of the viewcontrollers above), and then adds a duplicate UITabBar + custom view right on top of it.

Here's the meat of what I had to do. I uploaded a functioning example of it and can be found in this repo:

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
    }
}

Upvotes: 9

vrwim
vrwim

Reputation: 14380

Your idea to put it in a wrapper viewcontroller is good, but it will only cause overhead (more viewcontrollers to load in memory), and issues when you want to change the code later on. If you want the bar to always show on your UITabBarController, then you should add it there.

You should subclass UITabBarController and load the custom bar from a nib. There you will have access to the tabbar (so you can place your bar correctly above it), and you will only load it in once (which solves your problem that you will face having a different bar on each tab).

As for your views not reacting to the size of the custom bar, I don't know how you can do that, but my best suggestion is to use a public variable and notifications that you listen to in your individual tabs.
You can then use that to change the bottom constraint.

Upvotes: 2

Related Questions