闲扯淡
很久之前就想梳理一下 UIButton 的几个 edgeInsets 属性. 最近刚好项目需要用到,顺便整理下.
我们先看看这几个属性:
open var contentEdgeInsets: UIEdgeInsets
open var titleEdgeInsets: UIEdgeInsets
open var imageEdgeInsets: UIEdgeInsets
在需要修改系统系统的 UIButton 样式内部布局时, 最常见的场景就是将按钮 [左图右字] 的布局形式改为 [上图下字] , 或者修改 imageView 与 label 之间的间距, 以及按钮内容按照预期偏移.
之前开发中遇到过几次修改 insets 的, 虽然最后在左算右算的推测之下, 也算是完成了任务, 但下一次再遇见的时候又会忘掉. 今天来总结一下具体的数值影响对实际布局影响的方式吧.
先看 UIEdgeInsets 对 Frame 的影响吧, 先上结论!
UIEdgeInsets 对单个视图布局的影响
以原 frame
的边框为基准, 向内为正, 向外为负.最终的边框根据 edgeInsets
四个值的大小确定, 具体计算公式为:
-
对与 OrginalFrame、EdgeInset、ResultFrame 有:
-
对于 OriginalPosition、EdgeInsets、ResultPosition 有:
为什么要有个 position
的位移计算? 因为 UIButton
在对 imageView
和 titleLabel
进行布局的时候需要考虑 imageView.intrinsicContentSize
与 titleLabel.intrinsicContentSize
, 这种时候情况就会变得很复杂. edgeInsets
所起的作用会由于 intrinsicContentSize
而改变. 这种时候, frame 不再靠谱, 反而是 center
与 position
依旧坚守岗位. 所以在设置 imageEdgeInsets
与 titleEdgeInsets
时, 结果往往变得很迷惑. 就需要了解 UIButton
对子控件布局了, 请参阅UIButton 的布局 imageView 和 titleLabel 的过程. 可以稍微缓解下子控件布局的疑惑. 当然如果有更好的理解方法, 请告诉我. 我也很想知道.
contentEdgeInsets 实验
这个结论在设置 UIButton 的 contentEdgeInsets 时非常直白. 举个例子, 我们新建一个 100x100 pt
的 button, 并给它一张 100x100 pt
的图片,设置好颜色边框之后,让我们开始吧 :
let sampleBtn = UIButton(frame: CGRect(x: 40, y: 40, width: 100, height: 100));
sampleBtn.layer.borderColor = UIColor.white.cgColor
sampleBtn.layer.borderWidth = 1.0
sampleBtn.setImage(UIImage(named: "100"), for: .normal)
sampleBtn.imageView?.layer.borderWidth = 1.0
sampleBtn.imageView?.contentMode = .scaleToFill
sampleBtn.imageView?.layer.borderColor = UIColor.orange.cgColor
运行可得:
可以看到 imageView 和 UIButton 的边框完全重合. 在这种情况下, imageView的 frame 与 button.bounds 完全重合. 所以可以通过观察 imageView 的 frame 来观察具体偏移量的计算:
设置 sampleBtn
的 contentInsets 为 (-20, -20, 50, 30)
, 在按下 CMD+R
之前我们先按照上述规则计算一下, 之前的 content 的 frame 为(0, 0, 100, 100)
对应的 content 的 frame 应为: (-20, -20, 90, 70)
.
然我们来看看运行结果:
在布局之后打印 imageView 的 frame 可以得到:
image:(-20.0, -20.0, 90.0, 70.0)
与预期契合.
对仅有 imageView 的 button 设置 imageEdgeInsets 实验
同样, 设置 contentEdgeInsets
为 .zero
, 设置 imageEdgeInsets
的值, 可以得到以下截图(这里小小的做了个弊: image.size
与 button.frame.size
相等, 这是为了更直白的看出对子控件的影响. 在两者不相等时, 布局方式会由于需要考量image.size
变得不够直白):
是的, 与预期一致.
对仅有 titleLabel 的 button 设置 titleEdgeInsets 实验
对既有 titleLabel 又有 imageView 的 button 设置 imageEdgeInsets 与 titleEdgeInsets 实验
先把 button
的 contentEdgeInsets
置回 .zero
, 在本节, 我们不再修改 contentEdgeInsets
, 避免对观察结果造成影响:
调整 button
的 frame
为 (40, 40, 200, 100)
(当然这是由于 100x100 的大小装不下 label, 不方便我们观察), 给 button
的 label
设置下属性:
sampleBtn.titleLabel?.layer.borderWidth = 1.0
sampleBtn.titleLabel?.layer.borderColor = UIColor.cyan.cgColor
sampleBtn.setTitle("点击", for: .normal)
sampleBtn.setTitleColor(.white, for: .normal)
运行结果如下:
先来梳理下页面上的组件属性:
- button: frame(40, 40, 200, 100), borderColor: .while
- imageView: imageSize: 100x100, borderColor: .orange
- label: text: "点击", borderColor: .cyan
可以看到, 默认情况下, imageView
与 label
两个控件相连居中, 布局后打印下当前 imageView
与 label
的 frame
为:
image:(31.5, 0.0, 100.0, 100.0)
label:(131.5, 39.5, 37.0, 21.5)
我们来设置下 imageView
的 edgeInsets
并运行:
sampleBtn.imageEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10)
//sampleBtn.titleEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0)
再看看设置
label
的情况:
//sampleBtn.imageEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10)
sampleBtn.titleEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0)
在这种情况下, 情况似乎变得难以捉摸. 这是由于 imageView 和 titleLabel 都是有有效的intrinsicContentSize
的, UIButton
需要保证最佳的显示效果, 对 imageView
与 titleLabel
的 frame
采用了更复杂的计算机制, 而不是简单的计算偏移.
但在多次验证后, 可以发现虽然 frame
的计算方式由于多重因素影响, 不太好计算, 但 center
还是非常守规矩的. 也就是上面用 ResultFrame
可以推导出来的 ResultPoition
公式.
所以如果只是想简单的移动 UIButton
中 imageView
与 titleLabel
的 center
, 而不改变 imageView
与 titleLabel
的大小的话, 事情就变得简单了很多: 设置 edgeInsets
时只需要注意边距移动方向相同, 大小相等就可以了. 即, 两两相反, 即 top = - bottom
, left = - right
, 这种时候该视图就只会改变 origin
与 center
而不会改变 size
.
例如, 如果我们想要让图片上下排列, 只需要将 imageView
向上且向右移动, 设置 imageEdgeInsets = (a, b, -a, -b)
, 上和右, 则 a > 0
, b < 0
, 具体是多少?你可以根据 titleLabel.size
来决定. 将 titleLabel
向左下移动? 同理呀!
结论
UIEdgeInset
可以设置 View
的偏移量, 其方向为 frame
向中心方向为正, 而影响如定义的 top
、left
、bottom
、right
, 即为各个边框在向中心方向上的偏移量.
但在 UIButton
中的 imageView
与 titleLabel
同时存在时布局, 由于系统在处理这里的布局时需要考量到 intrinsicContentSize
, 如果希望设置edgeInsets
之后不造成变形, 请在 edgeInsets
时, 不要修改 imageView
和 titleLabel
的大小, 而仅进行移动位置操作.
如果对 EdgeInsets
如何影响 imageView
与 titleLabel
的 frame
, 可以移步
UIButton 的布局 imageView 和 titleLabel 的过程 一起讨论下.