UIButton 不完全按照 XIB 的 contentVerticalAlignment 值呈现

UIButton not render exactly as XIB's contentVerticalAlignment value

我想让按钮的顶部与按钮的 titleLabel 的顶部对齐,所以我在 xib 中将内容垂直对齐设置为顶部。这在 xib 中运行良好,但在构建之后,titleLabel 似乎仍然采用居中垂直对齐方式进行布局。我错过了什么?

首先,评论:您在 Storyboard / Interface Builder 中看到的内容:

  • 并不总是完全 UIKit 在模拟器中呈现的内容
  • 这并不总是 完全 UIKit 在设备上呈现的内容

这就是我们在不同的模拟器和设备上测试、测试、测试...的原因。


那么,这是怎么回事?

UIKit 使用按钮的框架 .titleLabel 来确定按钮的固有大小。

对于没有明确设置宽度或高度的默认按钮,UIKit 将标题标签插入顶部和底部 6-pts

这里有 2 个按钮 - 都没有高度限制。 按钮 1 是 center/center 的默认内容对齐方式,按钮 2 设置为 Left/Top。按钮标题标签背景是青色的,所以我们可以很容易地看到它的框架。

故事板/IB:

运行时间:

调试视图层次结构(注意标签框架与按钮框架):

因此,使用 18 磅系统字体,标题标签高度为 21.0 ... 添加 6-pts 顶部和底部,按钮框架高度为 33-pts

无论您将控件对齐设置为顶部/中心/底部还是填充都没有关系...UIKit 仍然采用标签高度并添加 6-pts 顶部和底部“填充”。

如何才能获得真正的顶部对齐?让我们看看几种方法。

堆栈视图中有 6 个按钮:

按钮 1 默认为 center/center,没有高度限制。

Button 2 如我们所见,控件左/上对齐...但对垂直对齐没有影响。

按钮 3 也是 Left/Top,但让我们给它一个明确的高度(我们将使用 80 使事情变得明显)。看起来更好 - 但标题标签的顶部仍添加了 6-pts 个“填充”。

现在,您可能已经在尺寸检查器面板的顶部看到了这个:

这看起来很有希望!让我们将 Title Insets Top 设置为零!

哎呀——已经是零了?!?!?!?

与内容插图相同!

事实证明,如果边缘插入是默认值,则无论如何都会添加填充。

我们可以尝试将 Title Inset Top 设置为 -6,并且由于标签将文本垂直居中,我们还必须将 Bottom inset 设置为 6。这行得通......但我们可能不想依赖 6 的值在未来的 iOS 版本中是准确的。

按钮 4 - 让我们尝试 Content Insets -> Bottom: 1 ... 看起来我们正在路上!标签现在 top-aligned 带有按钮框架!

所以...

Button 5 - 删除 Height: 80 约束,以便我们可以使用默认按钮高度。哦!按钮框架高度现在是 title-label-height 加上 zero-top 加上 one-bottom...

按钮 6 - 最后,我们将使用 Content Insets -> Bottom: 133(默认按钮高度)的显式高度约束。


那么...如果我们不想设置显式高度怎么办?也许我们稍后会更改标题标签的字体大小?或者我们 运行 陷入其他问题?

您可以使用自定义 UIButton 子类。

这是一个示例,标记为 @IBDesignable 这样我们就可以在 Storyboard / IB 中看到它的布局:

@IBDesignable
class MyTopLeftAlignedButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        commonInit()
    }
    func commonInit() {
        self.contentVerticalAlignment = .top
        self.contentHorizontalAlignment = .left
        // if we want to see the title label frame
        //titleLabel?.backgroundColor = .cyan
    }
    override var bounds: CGRect {
        didSet {
            if let v = titleLabel {
                var t = titleEdgeInsets
                let h = (bounds.height - v.frame.height) * 0.5
                t.top = -h
                t.bottom = h
                titleEdgeInsets = t
            }
        }
    }
}

编辑 - 回复评论...

如果您的目标是匹配“按钮 5”样式 - 按钮高度与标题标签高度匹配 - 最好的选择可能是...

Select 按钮,然后在 Size Inspector 面板中使用这些设置:

0.1 顶部和底部值将覆盖默认的 6-pt Top/Bottom“填充”,将其有效减少为零。