在iOS开发中,对于控件布局我们一般是使用AutoLayout加约束的机制实现,UIKit有一个布局组件UIStackView,它与Flutter中的Column和Row有点类似,我们可以使用这个控件实现横向或纵向上子视图的布局,好处是它本身不参与渲染,坏处是相对UIView有学习理解成本。
一、如何使用?
首先来了解几个重要的属性:
-open var axis: NSLayoutConstraint.Axis
这个属性有horizontal
、vertical
两种值,代表横向布局还是纵向布局子控件,默认是horizontal
。设定了这个的属性后,内部的arrangedSubviews不需要添加主轴方向上的约束(指的是vertical
时上下和horizontal
时左右)。
-open var alignment: UIStackView.Alignment
这个属性代表内部arrangedSubviews的对齐方式, 默认.fill
public enum Alignment : Int {
// 横向stack:贴紧顶部和底部,纵向stack则贴紧头部尾部
case fill = 0
// 横向stack:顶部对齐,纵向stack:头部对齐
case leading = 1
// 横向stack:顶部对齐(这是一个计算属性,返回的是leading)
public static var top: UIStackView.Alignment { get }
// 横向stack下:对齐视图内文本的第一行基线
case firstBaseline = 2 // Valid for horizontal axis only
// 沿着轴线方向居中对齐
case center = 3
// 横向stack:底部对齐,纵向stack:尾部对齐
case trailing = 4
// 横stack:底部对齐(这是一个计算属性,返回的是trailing)
public static var bottom: UIStackView.Alignment { get }
// 横向stack下:对齐视图内文本的最后一行基线
case lastBaseline = 5 // Valid for horizontal axis only
}
-open var distribution: UIStackView.Distribution
这个属性代表内部arrangedSubviews的排布方式, 默认.fill。
下面的解释中会涉及到约束的硬知识:
- 内容拥抱优先级(Content Hugging Priority):视图拒绝变为大于其固有大小的优先级,优先级越低越容易被拉伸。
- 内容压缩阻力优先级(Content Compression Resistance Priority):视图拒绝小于其固有大小的优先级, 优先级越低越容易被压缩
nameView1.setContentHuggingPriority(.defaultLow, for: .horizontal) nameView1.setContentHuggingPriority(.defaultLow, for: .vertical) nameView1.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) nameView1.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
如果对
内容拥抱优先级
、内容压缩阻力优先级
有疑问可查看 iOS开发之AutoLayout中的Content Hugging Priority和 Content Compression Resistance Priority解析
public enum Distribution : Int {
// 适用于使UIStackView包裹内容,
// 作用是调整内部arrangedSubviews,使它们沿着轴填充UIStackView的剩余可用空间
// 1.当内部视图总体超出UIStackView自身约束的高度/宽度时,
// UIStackView会根据内部视图的(内容压缩阻力优先级)来收缩,由优先级更低的来收缩;
// 2.当内部视图总体不足以排满UIStackView自身约束的高度/宽度时,
// UIStackView会根据内部视图的(内容拥抱优先级)来扩张,由优先级更低的来扩张;
// 3.如果优先级一致时,此时就有歧义,
// UIStackView会根据索引大小来决定,从索引最小(或者最大,不固定,按照实际开发时看到的情况决定)
// 的view开始收缩或者扩张,直至满足UIStackView的大小
// 4.如果UIStackView自身约束的高度/宽度是greaterThanOrEqualTo类型的,
// UIStackView会根据内容来伸缩。
case fill = 0
// 适用于需要使内部arrangedSubviews大小一致
// 使它们沿着轴填充UIStackView的可用空间,子视图大小相等排布
//(会忽略Subviews的width和height约束)
case fillEqually = 1
// 适用于UIStackView高度/宽度约束固定了,按内部arrangedSubviews宽度高度约束比例来沿着主轴方向排布。
// 子视图也需要设置高度/宽度约束,优先级要低于父视图约束比如:
// make.height.equalTo(height).priority(900) // 约束优先级默认1000
case fillProportionally = 2
// 适用于内部arrangedSubviews之间需要保持相等间隔
// 1.当内部arrangedSubviews的大小不足以在主轴方向填充UIStackView时,
// 会将剩余的空间均分给各个Subview之间间隔排布
// 2.当内部arrangedSubviews的大小超出UIStackView时,
// 会按照内容拥抱优先级来压缩(没设置优先级的话,会根据索引从第一个(或者最后一个,按照实际情况)开始压缩)
case equalSpacing = 3
// 1.当内部arrangedSubviews不足以排满UIStackView时,
// 会按照子视图之间相等的中心点距离排布,同时会保持最小的space(由space属性决定)
// 2.当内部arrangedSubviews超出时,会保持最小的space(由space属性决定),
// 并且根据内容压缩阻力优先级来压缩内部视图;如果优先级未设定,则会间隔一个地压缩subview
case equalCentering = 4
}
open var spacing: CGFloat
用来设置arrangedSubview之间的间隔,负值代表重叠;在fillProportionally
布局下是精确的间隔,在equalSpacing
,equalCentering
布局下代表最小的间隔。open var isBaselineRelativeArrangement: Bool
default is false! 用于纵向布局的stackView,用来设定垂直布局的view之间的空隙是否以上面view中最后一行文字的baseline,跟下面view的第一行文字的baseline来判断。open var isLayoutMarginsRelativeArrangement: Bool
default is false! 用于设定布局子view是否使用LayoutMargins
二、中途添加或者删除内部的view,排列会怎么样变化
我们使用UIStackView来布局内部子view时,子视图应该以下面方法来添加或者移除. 因为看方法很直白所以就不解释用法了,需要注意的是:
- 执行了下面方法添加或删除Subview,内部会重新布局一次。
- 添加的子视图也会添加到subviews属性数组中。
open func addArrangedSubview(_ view: UIView)
open func removeArrangedSubview(_ view: UIView)
open func insertArrangedSubview(_ view: UIView, at stackIndex: Int)
三、我的代码我的坑
1. 背景色
iOS 14以下,设置背景色是失效的,iOS14及以上可以设置背景色。