可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am experiencing an annoying problem testing the newest iOS 11 on the iPhone X simulator.
I have an UITabBarController
and inside each tab there is a UINavigationController
, each UINavigationBar
has defined also a bottom toolBar (setToolbarHidden:
), and by default they show up at the bottom, just over the tabBar.
It has been working fine so far and seems to work fine also in the upcomming iPhone 8 and 8 Plus models, but on the iPhone X there is a gap between the toolBar and the tabBar. My guess is that the toolBar doesn't realize that is displayed inside a tabBar and then leaves the accommodating space at the bottom.
I guess the only way to fix it would be using a custom toolbar and display/animate it myself instead of using the defaults UINavigationBar
, but I would like to hear other options :)
- This is how it looks on iPhone 8.
- And here is the problem on iPhone X.

回答1:
I filed this as radr://problem/34421298, which was closed as a duplicate of radr://problem/34462371. However, in the latest beta of Xcode 9.2 (9C32c) with iOS 11.2, this seems to be fixed. Here's an example of my app running in the simulator of each device, with no changes in between.

This isn't really a solution to your problem, other than that some patience may solve it without needing to resort to UI trickery. My assumption is that iOS 11.2 will be out before the end of the year, since it's needed to support HomePod.
回答2:
If you don't consider rotations you can try to manipulate the layer of the toolbar as a very hacky yet fast workaround.
class FixNavigationController: UINavigationController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) updateTollbarPosition() } func updateTollbarPosition() { guard let tabbarFrame = tabBarController?.tabBar.frame else { return } let gapHeight = tabbarFrame.origin.y-toolbar.frame.origin.y-toolbar.frame.size.height var frame = toolbar.layer.frame frame.origin.y += gapHeight toolbar.layer.frame = frame } }
Unfortunately, rotation animation doesn't look good when it comes to this approach. In this case, adding the custom toolbar instead of the standard one will be a better solution.
回答3:
iOS 11.1 and iPhone X are released and this bug/feature isn't fixed yet. So I implemented this workaround. This code works in iOS 9.0+.
Just set this class in your storyboard as navigation controller's class. It will use custom toolbar in iPhone X with correct layout constraints, and falls back to native one in other devices. The custom toolbar is added to navigation controller's view instead of your view controller, to make transitions smoother.
- Important Note: You have to call
updateItems(animated:)
manually after setting toolbarItems
of your view controller to update interface. If you set toolbarItems
property of navigation controller, you can ignore this step.
It simulates all native toolbar behavior (including changing toolbar height in portrait/landscape modes), except push/pop animations.
import UIKit class FixNavigationController: UINavigationController { private weak var alterToolbarHeightConstraint: NSLayoutConstraint? private var _alterToolbar: UIToolbar? private func initAlretToolbar() { _alterToolbar = UIToolbar() _alterToolbar!.isTranslucent = true _alterToolbar!.translatesAutoresizingMaskIntoConstraints = false view.addSubview(_alterToolbar!) if view.traitCollection.verticalSizeClass == .compact { alterToolbarHeightConstraint = _alterToolbar!.heightAnchor.constraint(equalToConstant: 32.0) } else { alterToolbarHeightConstraint = _alterToolbar!.heightAnchor.constraint(equalToConstant: 44.0) } let bottomAnchor: NSLayoutConstraint if #available(iOS 11.0, *) { bottomAnchor = _alterToolbar!.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) } else { bottomAnchor = _alterToolbar!.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor) } NSLayoutConstraint.activate([ _alterToolbar!.leadingAnchor.constraint(equalTo: view.leadingAnchor), _alterToolbar!.trailingAnchor.constraint(equalTo: view.trailingAnchor), bottomAnchor, alterToolbarHeightConstraint! ]) self.view.updateFocusIfNeeded() self.view.layoutIfNeeded() } private var alterToolbarInSuper: UIToolbar? { var superNavigationController = self.navigationController as? FixNavigationController while superNavigationController != nil { if superNavigationController?._alterToolbar != nil { return superNavigationController?._alterToolbar } superNavigationController = superNavigationController?.navigationController as? FixNavigationController } return nil } private var alterToolbar: UIToolbar! { get { if let t = alterToolbarInSuper { return t } if _alterToolbar == nil { initAlretToolbar() } return _alterToolbar } } // This is the logic to determine should use custom toolbar or fallback to native one private var shouldUseAlterToolbar: Bool { // return true if height is iPhone X's one return UIScreen.main.nativeBounds.height == 2436 } /// Manually call it after setting toolbar items in child view controllers func updateItems(animated: Bool = false) { if shouldUseAlterToolbar { (_alterToolbar ?? alterToolbarInSuper)?.setItems(viewControllers.last?.toolbarItems ?? toolbarItems, animated: animated) } } override var isToolbarHidden: Bool { get { if shouldUseAlterToolbar { return _alterToolbar == nil && alterToolbarInSuper == nil } else { return super.isToolbarHidden } } set { if shouldUseAlterToolbar { if newValue { super.isToolbarHidden = newValue _alterToolbar?.removeFromSuperview() _alterToolbar = nil self.view.updateFocusIfNeeded() self.view.layoutIfNeeded() // TODO: Animation when push/pop alterToolbarHeightConstraint = nil var superNavigationController = self.navigationController as? FixNavigationController while let superNC = superNavigationController { if superNC._alterToolbar != nil { superNC._alterToolbar?.removeFromSuperview() superNC._alterToolbar = nil superNC.view.updateFocusIfNeeded() superNC.view.layoutIfNeeded() } superNavigationController = superNC.navigationController as? FixNavigationController } } else { alterToolbar.setItems(viewControllers.last?.toolbarItems ?? toolbarItems, animated: false) } } else { super.isToolbarHidden = newValue } } } override func setToolbarItems(_ toolbarItems: [UIBarButtonItem]?, animated: Bool) { super.setToolbarItems(toolbarItems, animated: animated) updateItems(animated: animated) } override var toolbarItems: [UIBarButtonItem]? { get { return super.toolbarItems } set { super.toolbarItems = newValue updateItems() } } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { guard let _alterToolbar = _alterToolbar else { return } self.alterToolbarHeightConstraint?.isActive = false let height: CGFloat = (view.traitCollection.verticalSizeClass == .compact) ? 32.0 : 44.0 let alterToolbarHeightConstraint = _alterToolbar.heightAnchor.constraint(equalToConstant: height) alterToolbarHeightConstraint.isActive = true self.alterToolbarHeightConstraint = alterToolbarHeightConstraint } }
回答4:
I have found only one workaround: add toolbar directly to the view controller

回答5:
Apple still has not yet fixed this bug in iOS 11.2. Derived from Mousavian's solution, here is a simpler approach that I took.
I took this approach because I have just one UITableViewController where this bug happens. So in my case, I just added the following code listed below to my ViewController (which is UITableViewController) where this bug happens.
Advantages are:
- This fix just takes over in case of an iPhone X. No side effects to expect on other devices
- Works with any transition
- Works regardless of other parent/child controllers having Toolbars or not
- Simple
And here is the code:
1.Add startFixIPhoneXToolbarBug to your viewWillAppear like this:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) startFixIPhoneXToolbarBug() }
2.Add endFixIPhoneXToolbarBug to your viewWillDisappear like this:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) endFixIPhoneXToolbarBug() }
3.Implement start/endFixIPhoneXToolbarBug in your viewController like this:
private var alterToolbarHeightConstraint: NSLayoutConstraint? = nil private var alterToolbar: UIToolbar? = nil func startFixIPhoneXToolbarBug() { // Check if we are running on an iPhone X if UIScreen.main.nativeBounds.height != 2436 { return // No } // See if we have a Toolbar if let tb:UIToolbar = self.navigationController?.toolbar { // See if we already added our own if alterToolbar == nil { // Should always be the case if let tbView = tb.superview { // Create a new Toolbar and apply correct constraints alterToolbar = UIToolbar() alterToolbar!.isTranslucent = true alterToolbar!.translatesAutoresizingMaskIntoConstraints = false tb.isHidden = true tbView.addSubview(alterToolbar!) if tbView.traitCollection.verticalSizeClass == .compact { alterToolbarHeightConstraint = alterToolbar!.heightAnchor.constraint(equalToConstant: 32.0) } else { alterToolbarHeightConstraint = alterToolbar!.heightAnchor.constraint(equalToConstant: 44.0) } let bottomAnchor: NSLayoutConstraint if #available(iOS 11.0, *) { bottomAnchor = alterToolbar!.bottomAnchor.constraint(equalTo: tbView.safeAreaLayoutGuide.bottomAnchor) } else { bottomAnchor = alterToolbar!.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor) } NSLayoutConstraint.activate([ alterToolbar!.leadingAnchor.constraint(equalTo: tbView.leadingAnchor), alterToolbar!.trailingAnchor.constraint(equalTo: tbView.trailingAnchor), bottomAnchor, alterToolbarHeightConstraint! ]) tbView.updateFocusIfNeeded() tbView.layoutIfNeeded() } } // Add the original items to the new toolbox alterToolbar!.setItems(tb.items, animated: false) } } func endFixIPhoneXToolbarBug() { if alterToolbar != nil { alterToolbar!.removeFromSuperview() alterToolbar = nil alterToolbarHeightConstraint = nil if let tb:UIToolbar = self.navigationController?.toolbar { tb.isHidden = false } } }