Saren Inden
Saren Inden

Reputation: 3660

iOS doesn't show tab bar after popping a view controller with hidesBottomBarWhenPushed sets to true

I have an issue with the hidesBottomBarWhenPushed.

It works correctly when pushed (hides tab bar) and when popped (shows tab bar). But I have this little gem:

So sadly since there are no official functions to call on the UITabBarController to hide or show the UITabBar I don't see a good way to fix this. There is also no function to reevaluate the current state.

If the presentation is slightly delayed all works fine (but well there are some stakeholders who don't want that...)

So this is the complete code that reproduces the bug (assumes a new project with a storyboard where you have a UITabBarController as the entry point, in it a single UINavigationController with the ViewController as its child)

Please forgive the 'uglyness', it is just simple demo code

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tabBarController?.tabBar.isTranslucent = false
        
        view.backgroundColor = .red
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.navigationController?.pushViewController(VC2(), animated: true)
        }
    }
}


class VC2: UIViewController {
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        
        hidesBottomBarWhenPushed = true
        
        navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .close, primaryAction: UIAction(handler: { _ in
            let nvc = self.navigationController
            let vc = UINavigationController(rootViewController: VC3())
            vc.modalPresentationStyle = .fullScreen
            
            // With a delay it all works fine
//            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                nvc?.present(vc, animated: true)
//            }

            self.navigationController?.popViewController(animated: true)
        }), menu: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .blue
    }
}

class VC3: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .green
        
        navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .close, primaryAction: UIAction(handler: { _ in
            self.dismiss(animated: true)
        }))
    }
}

Upvotes: 0

Views: 117

Answers (2)

Saren Inden
Saren Inden

Reputation: 3660

And I found another solution. This only works if the order is something like

vc.present(modalVC, animated: true)
...
self.navigationController?.popViewController(animated: false)

and not the other way around (so the present must be done before the pop)

var animatedPop = true

if let presentedViewController, presentedViewController.modalPresentationStyle == .fullScreen {
    animatedPop = false
}

navigationController?.popViewController(animated: animatedPop)

Apparently for some magical reason the system does evaluate the hidesBottomBarWhenPushed correctly if not animated

Upvotes: 0

Saren Inden
Saren Inden

Reputation: 3660

Seems that a 'hacky' solution is to make a modification to the 'owning' UINavigationController

override public func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Determine if we are in the invalid state
    guard
        let tabBarController,
        tabBarController.tabBar.isHidden,
        let last = children.last,
        !last.hidesBottomBarWhenPushed
    else {
        return
    }

    // Force the navigation controller to reevaluate the current `hidesBottomBarWhenPushed`
    pushViewController(UIViewController(), animated: false)
    popViewController(animated: false)

    // The safe space is shown but the tab bar is still set to hidden
    tabBarController.tabBar.isHidden = false
}

For me this works without any visual artifacts, but it is also a bit hacky which is always a questionable solution.

Upvotes: 0

Related Questions