Problem with UITabBarController is that its TabBar is added without usage of NSLayoutConstraints (or, more precisely, it translates autoresizing mask into constraints). For this reason you can use two kinds of approach:
1) Use UITabBarController in the way you are doing in now, but it needs some hacks to hide it - basically use UITabBarController inside UINavigationController in order to push a view on top of it (but transition will be visible, even if you will push it without animation (keyboard will start hiding), or you can hide TabBar and resize frame of TabBar content view manually, as shown in https://stackoverflow.com/a/6346096/7183675).
In this last case you have also to remember frame of content view before changing it (or calculate it before unhiding TabBar again). Also, as it is not in the official API you have to take into account that order of subviews inside UITabBarController can change and effects can look really strange (or simply crash the app)
2) use "normal" UIViewController with UITabBar and its items added manually with constraints. It can be also custom UIView subclass and few buttons created from XIB. Here you are creating constraints directly, so you have better control.
But this one also won't go without some hacks because UITabBar added to single UIViewController goes together with this UIViewController with every transition (given that you have UINavigationController in every UIViewController it will be very often).
So in this case major issue is to make single bottom bar and transfer it to UIWindow on viewDidAppear of view where your one and only bottom bar is created - recommended from storyboard or xib file. For next view you will only pass reference to it or keep this pointer in one class for that. You should also remember to create view covering safe area under tab bar.
It would look like that:
private var firstRun = false
override func viewDidLoad() {
super.viewDidLoad()
firstRun = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard firstRun else {
bottomBar.superview?.bringSubviewToFront(bottomBar)
bottomSafeAreaView.superview?.bringSubviewToFront(bottomSafeAreaView)
return
}
guard let window = UIApplication.shared.windows.first, let bottomB = bottomBar, let bottomSafeArea = bottomSafeAreaView else { return }
if bottomB.superview != window {
bottomB.deactivateConstrainsToSuperview()
bottomSafeArea.deactivateConstrainsToSuperview()
window.addSubview(bottomSafeArea)
window.addSubview(bottomB)
let bottomLeft = NSLayoutConstraint(item: bottomSafeArea, attribute: .leading, relatedBy: .equal, toItem: window, attribute: .leading, multiplier: 1, constant: 0)
let bottomRight = NSLayoutConstraint(item: bottomSafeArea, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1, constant: 0)
let bottomBottom = NSLayoutConstraint(item: bottomSafeArea, attribute: .bottom, relatedBy: .equal, toItem: window, attribute: .bottom, multiplier: 1, constant: 0)
let leftConstraint = NSLayoutConstraint(item: bottomB, attribute: .leading, relatedBy: .equal, toItem: window, attribute: .leading, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: bottomB, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: bottomB, attribute: .bottom, relatedBy: .equal, toItem: bottomSafeArea, attribute: .top, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([bottomLeft, bottomRight, bottomBottom, leftConstraint, rightConstraint, bottomConstraint])
}
window.layoutIfNeeded()
DispatchQueue.main.async(execute: {
bottomB.superview?.bringSubviewToFront(bottomB)
bottomSafeArea.superview?.bringSubviewToFront(bottomSafeArea)
})
firstRun = false
}
Plus one utility method created in extension:
extension UIView {
func deactivateConstrainsToSuperview() {
guard let superview = self.superview else {return}
NSLayoutConstraint.deactivate(self.constraints.filter({
return ($0.firstItem === superview || $0.secondItem === superview)
}))
}
}
So a bit of code to write, but one time only. After that you will have TabBar that is easy to show or hide when necessary, using constraint between your "content view" and safe area this way
private func hideBottomBar() {
UIView.animate(withDuration: Constants.appAnimation.duration, animations: { [weak self] in
guard let self = self else { return }
self.bottomBar.isHidden = true
self.bottomBarHeightConstraint.constant = 0
self.bottomBar.superview?.layoutIfNeeded()
})
}
and
private func showBottomBar() {
UIView.animate(withDuration: Constants.appAnimation.duration, animations: { [weak self] in
guard let self = self else { return }
self.bottomBar.isHidden = false
self.bottomBarHeightConstraint.constant = Constants.appConstraintsConstants.bottomBarHeight
self.bottomBar.superview?.layoutIfNeeded()
})
}
as for height of view covering safe area (between bottom of tabBar and top of BottomLayoutGuide)
if #available(iOS 11.0, *) {
self.bottomSafeAreaViewHeightConstraint.constant = self.view.safeAreaInsets.bottom
} else {
self.bottomSafeAreaViewHeightConstraint.constant = 0
}
Hope it will be helpful, good luck!