[TOC]
arrangedSubviews
图中的四个子试图可以通过 arrangedSubviews
属性进行设置。
我们可以通过初始化方法进行设置 arrangedSubviews
。
let arrangedSubviews = [UIView()]
let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
添加一个试图到 arrangedSubviews
数组最后。
let addNewView = UIView()
stackView.addArrangedSubview(addNewView)
从 arrangedSubviews
移除指定的试图。
stackView.removeArrangedSubview(addNewView)
我们还可以交换一个试图所在的位置到0
stackView.insertArrangedSubview(addNewView, at: 0)
axis
我们想怎么知道试图是按照横向排列还是纵向排列的呢?我们有一个枚举可以使用。
open var axis: UILayoutConstraintAxis
我们可以通过设置 axis
为 .horizontal
为横向布局
stackView.axis = .horizontal
我们可以设置 axis
为 .vertical
为纵向布局
stackView.axis = .vertical
现在我们知道怎么设置横向布局还有纵向布局,我们想做下面的试图结构该如何做呢。
红色的试图大小为 375x50
,中间的间距是 20
。白色的试图大小为 51x33
;
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 375, height: 676)
view.backgroundColor = .white
let backgroundView = UIView()
backgroundView.backgroundColor = #colorLiteral(red: 0.5725490451, green: 0, blue: 0.2313725501, alpha: 1)
backgroundView.bounds = CGRect(x: 0, y: 0, width: 375, height: 50)
backgroundView.center = view.center
view.addSubview(backgroundView)
var arrangedSubViews:[UIView] = []
for _ in 0 ..< 5 {
let subView = UIView()
subView.backgroundColor = #colorLiteral(red: 0.501960814, green: 0.501960814, blue: 0.501960814, alpha: 1)
arrangedSubViews.append(subView)
}
let stackView = UIStackView(arrangedSubviews: arrangedSubViews)
stackView.frame = CGRect(x: 20, y: 9, width: 375 - 40, height: 50 - 18)
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fillEqually
stackView.spacing = 20
backgroundView.addSubview(stackView)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
上面的代码可以放在 Playground
看运行的效果。
我们可以在上面的代码中发现了三个起到决定作用的属性分别是 alignment
distribution
spacing
。
alignment
关于 alignment
是一个枚举。
fill
fill
是铺满的样式。在保证最小间距的情况下,铺满整个 UIStackView
。这个模式之下,并不是均等的分割平铺,除非设置下面的属性。
stackView.distribution = .fillEqually
top
top
是基于顶部对其,下面根据再自动约束。
bottom
bottom
是基于底部对其,上面再进行自动约束。
对于横向布局来说
leading = top
trailing = bottom
横向布局设置左对齐和右对齐无法生效。
Center
center
当为横向布局的时候,约束像上下两端延伸。如果是纵向约束的时候,约束像左右两端延伸。
leading
leading
当布局方式为纵向排列时候,按照左侧对其。
trailing
trailing
是在纵向布局情况下,按照右侧进行布局。
firstBaseLine
firstBaseLine
只针对于横向排列布局有效,是针对数组第一个试图,如果不是文本就按照顶端开始对其,是文本按照文本下端对其。
lastBaseLine
lastBaseLine
也是针对于横向排列布局有效,是针对数组第一个试图,如果不是文本就按照底部开始对其,是文本按照文本底部对其。
distribution
distribution
这个属性是决定子试图布局的最关键因素。
fill
fill
模式是按照设置的间距,之后根据子试图全部铺满整个 UIStackView
。在这里我们要提到一个方法那就是 intrinsicContentSize
。
intrinsicContentSize
指代一个试图最适合的大小,对于 UIImageView
UILabel
UIButton
都是可以系统计算 intrinsicContentSize
大小的。
但是对于 UIView
是系统无法知道 intrinsicContentSize
大小。我们就需要子类重写 intrinsicContentSize
这个方法让系统知道最适合的试图大小是多少。
我们为什么要知道 intrinsicContentSize
的大小,首先我们研究 fill
形式试图布局的计算公式。
我们按照上图所以计算
let width = View1.intrinsicContentSize.width + spance + View2.intrinsicContentSize.width + spance + View3.intrinsicContentSize.width + spance + View4.intrinsicContentSize.width
当我们计算出的 Width
已经超过我们 UIStackView
的宽度的时候,我们会按照优先级,压缩优先级比较低的试图。
如果优先级都一样,那么我们会先保证 arrangedSubviews
数组里面排序在前面的试图展示,之后压缩排序在后面的试图。
当我们计算出的 width
不足以铺满我们 UIStackView
宽度的时候,我们会让优先级别叫高的试图优先按照正确的试图显示,当优先级一样的时候,我们会优先让 arrangedSubviews
索引在后面试图优先按照正常尺寸显示,最前面进行拉伸。
这里先讲解一下优先级,优先级分为抗压缩(Content Compression Resistance Priority
)还有抗拉伸(Content Hugging Priority
)。
Content Compression Resistance Priority
抗压缩的优先级: 750
优先级越高越不容易被压缩。
Content Hugging Priority
抗拉伸的优先级: 250(251)
优先级越高越不容易被拉伸。
这里有个奇怪的问题 代码创建优先级默认为
250
但是XIB
创建的优先级别为251
。
我们往 UIStackView
添加三个 UIView
,分别是红蓝黄。
我们发现能可见的试图只有 蓝色
的试图,这到底是怎么的回事呢?
因为对于 UIView
来说,系统无法计算试图的大小,会返回默认为(-1,-1)
。
对于显示来说,(-1,-1)
是按照 (0,0)
计算的。为什么出现蓝色,我也是猜测,但是经过几次实验来说,大概得出来这个结论。
对于
UIStackView
布局的数组来说,如果数组只有一个试图的时候是铺满整个
UIStackView
.如果数组试图大于一个的时候,左侧的边距是最后第二试图和第一个试图间隙想加。显示最后第二个试图。
就按照上面的例子来说。
最后第二个元素是
蓝色
的,所以显示出来的是蓝色试图的。和第一个试图只有一个间隙,我们设置间隙最小10
。那么蓝色试图左侧的间隙是是1*10
间距,右侧就是和最后一个的间隙10
。如果只有一个试图的时候。
因为不存在倒数第二个试图,直接显示唯一的一个。一个试图的左侧和右侧也没有试图,那就是没有间距,全部铺满。
为了证明我们 intrinsicContentSize
这个值决定 UIView
在 UIStackView
的布局,我们就用自定义 UIView
一个类,重写 intrinsicContentSize
方法,控制 红
,黄
,蓝
的大小为(100,0)
为什么我们不设置高度
,因为对于横向布局铺满来说。设置高度已经没有什么意义了,布局只关系宽度。
我们设置 UIStackView
的宽度为320
,间距为10
。
我们发现我们的三个试图竟然全部的展示出来,因为 100 + 10 + 100 +10 + 100 = 320
我们设置的大小和间距正好等于我们设置 UIStackView
的宽度。
我们设置一下 UiStackView
小于我们计算的宽度,设置为 280
。
当我们设置UIStackView
宽度不够的时候,我们的小黄被压缩了。因为要优先的保证间距,三个试图的优先级是一样的,所以小黄的只能委屈的被压缩了。
小黄再想凭什么我就被欺负,小红和小蓝说因为你来的比我们晚。后来小黄因为关系,一下子级别比小红和小蓝都高。没法再欺负了,怎么提升级别,看下面。
因为小黄默认抗压缩级别是750
,小黄通过关系提升到1000
,让你们还欺负我。
我们从结果来看,我们的小黄自从提升级别之后再也不被欺负,但是小蓝成为下一个挤压。
我们再把小黄的级别降下来,现在让容器变大,宽度变成375
。
因为
Playground
里面的预览模拟器屏幕的宽度就是375
我们发现小蓝和小黄已经恢复正常了,但是红色竟然被拉长了。这是什么原因呢?
我们想让小蓝拉伸,因为抗拉伸优先级越高越被拉伸,我们设置小蓝如下。
view2.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal)
我们可怜的小蓝果然变得臃肿了起来,因为 fittingSizeLevel
的优先级为50
,是最先考虑被拉伸的。
从上面我们得出一个结论如下:
当我们设置为
fill
模式填充的时候如果全是
UIView
计算不出来实际大小,就展示倒数第二个试图,如果只有一个就全部铺满如果宽度不够,就优先压缩优先级比较低的试图,其次从最后一个试图进行压缩。
如果宽度足够,就优先拉伸优先级比较低的试图,其次从最前面的试图进行拉伸。
fillEqually
这个模式是让所有的试图进行平分,如果宽度只够显示最小的间隙就只显示间隙。
这个模式不用计算实际大小,也不用设置优先级。
fillProportionally
这个是按照实际大小按照比例进行分配大小,暂时没找到分配到底有什么规律。
equalSpacing
这个模式是按照实际大小布局,如果宽度足够就平均拉伸间距。
如果宽度不足,就按照优先级之后试图顺序进行压缩。
equalCenter
保证最小间距情况下,按照最适合的大小,之后按照中心点距离相等进行排列。排列出来的间距不一定相等,但是最小必须等于设置的最小间距。
如果宽度小于设置的宽度,那么按照优先级之后按照试图顺序压缩试图。
如果宽度不够,就优先排列宽度最小的一边。如果最小在左侧就从左侧布局
如果最小的在右侧就从右侧布局
isLayoutMarginsRelativeArrangement
是否允许启用边缘布局,默认是 false
全部铺满。
假设我们设置isLayoutMarginsRelativeArrangement
为 true
,边距都为10。
setCustomSpacing
可以设置某个试图后的间距,这个设置优先级比默认间距优先级高。比如我们设置红色和蓝色中间的间距为5
。
关于 UIStackView
今天算是研究完,但是文章中有很多不正确的地方,大家发现不正确的地方及时提醒我纠正。