iOS 11 和 iPhone X:UINavigationBar 的工具栏间距在嵌入到 UITabBarController 时不正确

Posted

技术标签:

【中文标题】iOS 11 和 iPhone X:UINavigationBar 的工具栏间距在嵌入到 UITabBarController 时不正确【英文标题】:iOS 11 & iPhone X: UINavigationBar's toolbar spacing incorrect when embedded in UITabBarController 【发布时间】:2017-09-13 19:46:41 【问题描述】:

我在 iPhone X 模拟器上测试最新的 ios 11 时遇到了一个烦人的问题。

我有一个UITabBarController,每个选项卡内都有一个UINavigationController,每个UINavigationBar 还定义了一个底部工具栏(setToolbarHidden:),默认情况下它们显示在底部,就在tabBar 上方.

到目前为止,它运行良好,并且在即将推出的 iPhone 8 和 8 Plus 型号中似乎也运行良好,但在 iPhone X 上,工具栏和选项卡栏之间存在差距。我的猜测是toolBar没有意识到它显示在tabBar内部,然后将容纳空间留在了底部。

我想修复它的唯一方法是使用自定义工具栏并自己显示/动画它,而不是使用默认值 UINavigationBar,但我想听听其他选项 :)

这是它在 iPhone 8 上的样子。 这是 iPhone X 上的问题。

【问题讨论】:

你找到解决这个问题的方法了吗?我遇到了类似的问题。 @AtWork 没错,我只是想指出两个空格是相似的,这意味着问题的根源是工具栏没有意识到下面有一个标签栏... 我刚刚在 Apple 的 iOS HIG 中发现了一个提示框,位于 Toolbars 页面和标签栏页面的底部。最后一句写道,“标签栏和工具栏永远不会同时出现在同一个视图中。” 鉴于这个问题的存在是因为它违反了他们的设计准则,我担心 Apple 可能不会迅速解决这个问题。我不希望 Apple 在发布时发布针对 iPhone X 的修复程序,而是尝试从我的标签栏场景中删除所有工具栏。 这似乎已在 iOS 11.2 中修复,使用 Xcode 9.2 beta (9C32c) 的模拟器。 它似乎又回到了 iOS 13(测试版)和 Xcode 11(测试版)中。我当然希望这是一个苹果的错误。我会提交一份错误报告。 【参考方案1】:

我将其归档为 radr://problem/34421298,它作为 radr://problem/34462371 的副本被关闭。但是,在带有 iOS 11.2 的 Xcode 9.2 (9C32c) 的最新测试版中,这似乎已得到修复。这是我的应用在每台设备的模拟器中运行的示例,两者之间没有任何变化。

这并不是您的问题的真正解决方案,除了一些耐心可能会解决它而无需诉诸 UI 技巧。我的假设是 iOS 11.2 将在今年年底前发布,因为它需要支持 HomePod。

【讨论】:

【参考方案2】:

如果您不考虑旋转,您可以尝试将工具栏的图层作为一种非常简单但快速的解决方法来操作。

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
        

不幸的是,当涉及到这种方法时,旋转动画看起来并不好。在这种情况下,添加自定义工具栏而不是标准工具栏将是更好的解决方案。

【讨论】:

【参考方案3】:

我只找到了一种解决方法:将工具栏直接添加到视图控制器

【讨论】:

谢谢,但正如我在原始问题中所说:“我想修复它的唯一方法是使用自定义工具栏并自己显示/动画它,而不是使用默认的 UINavigationBar,但我想听听其他选择:)"【参考方案4】:

iOS 11.1 和 iPhone X 已发布,但此错误/功能尚未修复。所以我实施了这个解决方法。此代码适用于 iOS 9.0+。

只需在情节提要中将此类设置为导航控制器的类即可。它将在 iPhone X 中使用具有正确布局约束的自定义工具栏,并在其他设备中回退到本机工具栏。自定义工具栏被添加到导航控制器的视图而不是您的视图控制器中,以使过渡更平滑。

重要提示:在设置视图控制器的toolbarItems 更新界面后,您必须手动调用updateItems(animated:)。如果你设置了导航控制器的toolbarItems属性,可以忽略这一步。

它模拟所有原生工具栏行为(包括在纵向/横向模式下更改工具栏高度),推送/弹出动画除外。

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
    

【讨论】:

【参考方案5】:

Apple 尚未在 iOS 11.2 中修复此错误。源自 Mousavian 的解决方案,这是我采用的一种更简单的方法。

我采用这种方法是因为我只有一个 UITableViewController 会发生此错误。因此,就我而言,我只是将下面列出的以下代码添加到发生此错误的 ViewController(即 UITableViewController)中。

优点是:

此修复仅适用于 iPhone X。在其他设备上不会产生任何副作用 适用于任何过渡 无论其他父/子控制器是否有工具栏都可以工作 简单

这里是代码:

1.将 startFixIPhoneXToolbarBug 添加到您的 viewWillAppear 中,如下所示:

override func viewWillAppear(_ animated: Bool)

    super.viewWillAppear(animated)

    startFixIPhoneXToolbarBug()

2.将 endFixIPhoneXToolbarBug 添加到您的 viewWillDisappear 中,如下所示:

override func viewWillDisappear(_ animated: Bool)

    super.viewWillDisappear(animated)

    endFixIPhoneXToolbarBug()

3.在您的 viewController 中实现 start/endFixIPhoneXToolbarBug,如下所示:

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
        
    

【讨论】:

以上是关于iOS 11 和 iPhone X:UINavigationBar 的工具栏间距在嵌入到 UITabBarController 时不正确的主要内容,如果未能解决你的问题,请参考以下文章

适配ios11与iphone x实践

iOS 11 iPhone X 模拟器 TabBar 图标和标题呈现在顶部相互覆盖

iOS 11.1 到 11.2,iPhone X 有没有更耐用?

iOS 11 和 iPhone X:UINavigationBar 的工具栏间距在嵌入到 UITabBarController 时不正确

iOS 11.3曝光,这一功能iPhone X专属?

Cordova iOS 11.0 Iphone X 状态栏差距