iOS布局方式总结

iOS布局方式总结

1. frame布局。

性能相对比较好,但当views比较多,view依赖关系比较复杂或适配不同机型时,处理起来会比较繁琐,代码可读性低。特别在数据变化或横竖屏切换导致界面布局变化,通常要重新计算每个视图的frame,工作量巨大。

2. autoresizing布局。通过设置UIView的autoresizingMask属性来设置布局方式组合。

缺点:描述界面变化规则不够灵活,很多变化规则根本无法精确描述。

变化规则只能基于父视图与子视图之间,无法建立同级视图或者跨级视图之间的关系。

3. Auto Layout(NSLayoutConstraint)。

Cassowary的布局算法,通过将布局问题抽象成线性不等式,并分解成多个位置间的约束, Apple 在iOS 6推出的 Auto Layout(NSLayoutConstraint),内部使用的是该算法。

NSLayoutConstraint包含firstItem(约束视图),secondItem(参照视图),firstAttribute(约束视图的属性),secondAttribute(参照视图的属性),relation(关系,包括>=,=,<=), multiplier(比例系数),constant(常量),priority(优先级)。

约束属性中NSLayoutAttributeBaseline代表相对基线对齐。比如在UILabel中,基线是文字底部的位置,相对bottom略高。在大部分view中,基线和底部是一致的。

iOS 8对约束属性增加了一系列带上Margin的布局属性,类似CSS里的padding,比如NSLayoutAttributeLeftMargin。相对于NSLayoutAttributeLeft的左对齐,NSLayoutAttributeLeftMargin一般会在左边留出8个距离作为margin,可通过layoutMargins属性修改。

priority优先级只有在两个约束有冲突的时候才起作用,优先级高的会覆盖优先级低的,最高的优先级为1000。

3.1 translatesAutoresizingMaskIntoConstraints。

UIView有个translatesAutoresizingMaskIntoConstraints属性,对于用代码创建的view,默认值是true。translatesAutoresizingMaskIntoConstraints会将 frame/autoresizing布局 自动转化为 auto layout布局,转化的结果是为这个视图自动添加所有需要的约束,如果我们这时给视图添加自己创建的约束就一定会约束冲突。为了避免约束冲突,需要设置translatesAutoresizingMaskIntoConstraints = false。

3.2 UILayoutGuide。

如果要实现布局 对多个view之间的magin动态约束(margin的值不是固定,值受到布局约束),或者实现多控件共同居中,一种常见的实现方式是使用一个或多个辅助view,专门用于实现它们的约束关系。但这种辅助view会增加view视图复杂度,并会加入到事件响应路由中。iOS 9 便推出了UILayoutGuide来代替这种辅助view,UILayoutGuide直接继承自NSObject,并没有真正的创建一个View,只是创建了一个矩形空间,只在进行auto layout时参与进来计算。

3.2 safeAreaLayoutGuide(继承自UILayoutGuide)。

iOS 11 增加了safeAreaLayoutGuide 和 safeAreaInsets作为UIView的安全区属性。safeAreaLayoutGuide用于自动布局下对子视图建立与安全区域的约束,safeAreaInsets用于frame布局,返回view四个方向与安全区域的偏移量。safeAreaInsets在viewDidLoad获取不到真实的值,可以在viewSafeAreaInsetsDidChange获取。

4. NSLayoutAnchor。iOS 9 推出的自动布局类,通过设置view的不同锚来实现自动布局约束,内部可以理解成也是NSLayoutConstraint实现。NSLayoutAnchor相对NSLayoutConstraint,代码更加整洁,优雅,易读。

4. VFL。Visual Format Language 可视化格式语言是苹果公司为了简化Autolayout的编码而推出的抽象语言。通过一个抽象后的字符串描述视图的自动布局约束,简化了代码,增加了可读性。

5. 自动布局SnapKit/Masonry。主流使用的自动布局框架,它们使用链式编程的方式对NSLayoutConstraint进行了二次封装。举个例子:

make.bottom.lessThanOrEqualTo(contentView.snp.bottom).multipliedBy(0.5).offset(-10). priority(.low)

可以理解成NSLayoutConstraint的如下伪代码。

firstItem.firstAttribute.relation(secondItem. secondAttribute). multiplier. constant.priority

从snapKit源码可以得知,SnapKit会自动将view的translatesAutoresizingMaskIntoConstraints设置为false。对于使用了snapKit的view,关闭布局向auto layout隐式转换。

extension LayoutConstraintItem {

internal func prepare() {

if let view = self as? ConstraintView {

view.translatesAutoresizingMaskIntoConstraints = false

}

}

}

5.1高级用法汇总:

5.1.1 对单个约束进行操作。

var labelConstraint: Constraint?

label.snp.makeConstraints { (make) in

make.top.equalToSuperview()

make.right.lessThanOrEqualToSuperview()

labelConstraint = make.right.lessThanOrEqualTo(button.snp.left).constraint

}

// 关闭约束

labelConstraint?.deactivate()

// 开启约束

labelConstraint?.activate()

// 更新约束

labelConstraint?.update(offset: -10)

// 更改优先级

labelConstraint?.update(priority: .low)

5.1.2 contentHuggingPriority 和 ContentCompressionResistancePriority。

UILabel、UIImageView、UIButton 在没有设置size约束的时候,会使用数据填充计算后的intrinsicContentSize作为视图的size约束。contentHuggingPriority(拒绝放大优先级) 和 ContentCompressionResistancePriority(拒绝压缩优先级)常用于多个使用intrinsicContentSize作为自身size约束的视图,在相互存在水平或垂直方向关联约束,导致视图需要压缩或放大的拒绝优先级,拒绝优先级低的视图优先放大/压缩。

在使用拒绝压缩优先级时,若要指定视图满足最小宽度,此时在极限情况,所有视图都会出现压缩,因此需要将宽度优先级设置最高(大于所有的缩小优先级)

let label1 = UILabel()

label1.text = "111111111111111111111111111111111111111"

view.addSubview(label1)

let label2 = UILabel()

label2.text = "222222222222222222222222222222222222222222"

view.addSubview(label2)

label1.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

label2.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)

label1.snp.makeConstraints { (make) in

make.left.equalToSuperview()

make.top.equalTo(50)

// label1宽度优先级大于label2的压缩优先级

make.width.greaterThanOrEqualTo(50).priority(.required)

}

label2.snp.makeConstraints { (make) in

make.right.equalToSuperview()

make.top.equalTo(50)

make.left.equalTo(label1.snp.right)

}

5.1.3 UILayoutGuide。

使用 UILayoutGuide 作为虚拟占位布局对象,可以实现多控件居中,动态margin等约束效果,同3.2。

使用UILayoutGuide实现动态margin,三等分间距效果:

func test() {

let blueView = UIView()

blueView.backgroundColor = .blue

view.addSubview(blueView)

let redView = UIView()

redView.backgroundColor = .red

view.addSubview(redView)

let leftLayoutGuide = UILayoutGuide()

let middleLayoutGuide = UILayoutGuide()

let rightLayoutGuide = UILayoutGuide()

view.addLayoutGuide(leftLayoutGuide)

view.addLayoutGuide(middleLayoutGuide)

view.addLayoutGuide(rightLayoutGuide)

blueView.snp.makeConstraints { (make) in

make.height.width.equalTo(50)

make.top.equalTo(100)

}

redView.snp.makeConstraints { (make) in

make.height.width.equalTo(50)

make.top.equalTo(100)

}

leftLayoutGuide.snp.makeConstraints { (make) in

make.left.equalToSuperview()

make.right.equalTo(blueView.snp.left)

}

middleLayoutGuide.snp.makeConstraints { (make) in

make.left.equalTo(blueView.snp.right)

make.right.equalTo(redView.snp.left)

make.width.equalTo(leftLayoutGuide)

}

rightLayoutGuide.snp.makeConstraints { (make) in

make.right.equalToSuperview()

make.left.equalTo(redView.snp.right)

make.width.equalTo(leftLayoutGuide)

}

}

UILayoutGuide实现动态margin

使用UILayoutGuide实现多控件居中:

func test() {

let blueView = UIView()

blueView.backgroundColor = .blue

view.addSubview(blueView)

let redView = UIView()

redView.backgroundColor = .red

view.addSubview(redView)

let layoutGuide = UILayoutGuide()

view.addLayoutGuide(layoutGuide)

blueView.snp.makeConstraints { (make) in

make.height.equalTo(50)

make.width.equalTo(100)

make.top.equalTo(100)

}

redView.snp.makeConstraints { (make) in

make.height.width.equalTo(50)

make.top.equalTo(100)

make.left.equalTo(blueView.snp.right).offset(20)

}

layoutGuide.snp.makeConstraints { (make) in

make.centerX.equalToSuperview()

make.left.equalTo(blueView.snp.left)

make.right.equalTo(redView.snp.right)

}

}

UILayoutGuide实现多控件居中

5.1.4 在父视图高度不确定,受数据填充和多个子视图布局影响。可以通过对多个可能的底部视图分别设定make.bottom.lessThanOrEqualTo/make.bottom.lessThanOrEqualToSuperview(),实现父视图动态高度。

5.1.5 对父视图调用layoutIfNeeded()使约束立即生效(自身调用只有size生效),可在动画中使用产生约束动画。

label1.superview?.setNeedsLayout()

UIView.animate(withDuration: 2) {

label1.snp.updateConstraints { (make) in

make.top.equalTo(200)

}

label1.superview?.layoutIfNeeded()

}

5.1.6 使用safeAreaLayoutGuide属性,将视图放在安全区域内。

func test() {

let redView = UIView()

redView.backgroundColor = UIColor.red.withAlphaComponent(0.5)

view.addSubview(redView)

redView.snp.makeConstraints { (make) in

make.edges.equalTo(self.view)

}

let blueView = UIView()

blueView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)

view.addSubview(blueView)

blueView.snp.makeConstraints { (make) in

make.edges.equalTo(self.view.safeAreaLayoutGuide)

}

}

紫色区域为安全区域

即使不是VC的视图,获取的safeAreaLayoutGuide也是在安全区域中。

func test() {

let testView = TestView()

view.addSubview(testView)

testView.snp.makeConstraints { (make) in

make.left.right.equalTo(self.view.safeAreaLayoutGuide)

make.top.equalToSuperview()

make.height.equalTo(120)

}

}

class TestView: UIView {

override init(frame: CGRect) {

super.init(frame: frame)

setupUI()

}

required init?(coder: NSCoder) {

fatalError("init(coder:) has not been implemented")

}

func setupUI() {

backgroundColor = .gray

let label = UILabel.init()

label.numberOfLines = 0

label.text = "123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123"

addSubview(label)

label.snp.makeConstraints { (make) in

make.edges.equalToSuperview()

// make.edges.equalTo(self.safeAreaLayoutGuide)

}

}

}

未使用safeAreaLayoutGuide,label范围超出安全区域

将 make.edges.equalToSuperview() 改成 make.edges.equalTo(self.safeAreaLayoutGuide)

// make.edges.equalToSuperview()

make.edges.equalTo(self.safeAreaLayoutGuide)

label范围在安全区域以内

5.1.7 可通过additionalSafeAreaInsets 修改VC的安全区域范围。

self.additionalSafeAreaInsets = UIEdgeInsets(top: 20.0, left: 50.0, bottom: 50.0, right: 50.0)

通过dditionalSafeAreaInsets缩小VC安全区域范围

UIView的insetsLayoutMarginsFromSafeArea属性默认为true,代表layoutMargin属性会加上safeArea,设为false,则不会加上safeArea。

5.1.8 UIScrollView 中的 safe area。

在iOS 11以前,当automaticallyAdjustsScrollViewInsets属性为true,导航栏为半透明,VC的加入的第一个scrollView会自动调整其contentInset,以保证滑动视图里的内容不被UINavigationBar与UITabBar遮挡。contentInset是实际的inset。

在iOS 11或以后,取代成UIScrollView的contentInsetAdjustmentBehavior属性,当scrollView超出安全区域,会调整inset以防止scrollView的内容超出安全区域。contentInset 是用户自定义的inset,adjustedContentInset是实际的inset,并且是只读属性。可以理解成 contentInset + contentInsetAdjustmentBehavior调整的inset = adjustedContentInset(实际inset)。

func test() {

scrollView.backgroundColor = .purple

scrollView.contentInsetAdjustmentBehavior = .always

view.addSubview(scrollView)

scrollView.snp.makeConstraints { (make) in

make.edges.equalToSuperview()

}

let label = UILabel()

label.text = "123123123123"

label.textColor = .white

scrollView.addSubview(label)

label.snp.makeConstraints { (make) in

make.left.top.equalToSuperview()

}

}

label显示在安全区域以内

UITableView 有个insetsContentViewsToSafeArea属性,会调整自动调整显示内容在安全区域以内,默认为true。

6. UIStackview

7. SDAutoLayout

🎈 相关推荐

旅游攻略导航
beat365官方网站正规

旅游攻略导航

📅 09-21 👀 3091
【攻略】菇菇王國完整全攻略~人人都是   菇菇王國守護者! 大家一起守護菇菇王國~ @新楓之谷 哈啦板
《剑网3》巴陵县刷马点位置介绍
约彩365安卓老版本

《剑网3》巴陵县刷马点位置介绍

📅 07-14 👀 9105