Auto Layout iOS 11 Toolbar UIBarButtonItem with customView

Auto Layout iOS 11 Toolbar UIBarButtonItem with customView

最近在我们的项目中,使用自定义视图的 UIBarButtonItem 出现问题。在 iOS 11 之前,我们通过灵活的间距项进行布局。这不再有效,因此没有显示任何内容。

因为我没有在 SO 上找到真正为我解决问题的答案,所以我对其进行了调查并想出了一个(诚然有点老套)的解决方案,我想与您分享。

也许它可以帮助您或者您有一些反馈。这是混合 objc 和 swift 代码,希望您不要介意。

如 WWDC 视频所示Updating Your App for iOS 11

"And so now in iOS 11, UI toolbar and UI navigation bar both have intricate and express support for auto layout."

所以我的第一步是对自定义视图本身使用布局约束:

    UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:customView];
    [barButtonItem.customView.widthAnchor constraintEqualToConstant:375].active = YES;
    [barButtonItem.customView.heightAnchor constraintEqualToConstant:44].active = YES;

这导致工具栏显示自定义视图。问题是视图的左侧和右侧存在间隙。你可以看到它。 所以我查看了 View Hierarchy Debugging 工具并意识到,工具栏上有一个 UIToolbarContentView。这个 contentView 有合适的大小(特别是宽度),我开始怀疑。我查看了 contentView 的唯一子视图,它是一个 UIBarButtonStackView。这个 stackView 在宽度方面以某种方式限制了我的 customView。

所以看起来像这样:

contentView |<-fullWidth-------->|
stackView     |<-reducedWidth->|
customView    |<-reducedWidth->|

让我好奇的是,customView 不是 stackView 的子视图。这可能是自定义视图包含在 UIBarButtonItem 中的结果。 customView 上的任何(附加)约束保持不变(或崩溃,因为视图不在同一层次结构中)。

学习完这些后,我给 UIToolbar 添加了一个扩展:

extension UIToolbar {
    private var contentView: UIView? {
        return subviews.find { (view) -> Bool in
            let viewDescription = String(describing: type(of: view))
            return viewDescription.contains("ContentView")
        }
    }

    private var stackView: UIView? {
        return contentView?.subviews.find { (view) -> Bool in
            let viewDescription = String(describing: type(of: view))
            return viewDescription.contains("ButtonBarStackView")
        }
    }

   func fitContentViewToToolbar() {
        guard let stackView = stackView, let contentView = contentView else { return }
        stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        stackView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true

    }
}

所以它的作用是这样的: 它通过将名称与 "ContentView" 进行比较来从子视图中获取 contentView,并通过对 contentView 执行相同的操作来获取 stackView。 可能 return subviews.first 会做同样的事情,但我想确定一下。

然后设置布局约束,瞧:它在全宽下工作。

我希望有人会觉得这有用。如果有评论:我非常愿意接受对此的反馈。也许我错过了什么,这一切甚至都没有必要。

编辑:'find' 函数是序列的扩展。它 'filter.first' 看起来像这样:

extension Sequence {
    func find(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> Self.Element? {
        return try filter(isIncluded).first
}

}