iOS AutoLayout

iOS AutoLayout

  • Autolayout是一种全新的布局技术,专门用来布局UI界面的,用来取代Frame布局在遇见屏幕尺寸多重多样的问题。Autolayout自iOS 6开始引入,但是由于Xcode 4的不给力,当时并没有得到大规模推广。在iOS 7(Xcode5)开始,Autolayout的开发效率得到很大的提升,苹果官方也推荐开发者尽量使用Autolayout来布局UI界面,减少纯代码的方式。在iOS6之前是用Autoresize布局.
  • Autolayout的好处是能够很方便的适配各种尺寸的屏幕,并且当很好的适配屏幕的横向和竖向,不会造成使用在有些手机上显示不全,或者横竖屏后布局错位的问题.
  • Autolayout父视图去添加对子视图的约束,Autoresize是子视图添加都父视图的相对位置和大小.

AutoLayout的使用

  • AutoLayout常见的使用方式有两种:
    1.IB方式即StoryBoard,Xib;
    2.纯代码方式即VFL,NSLayoutConstraint,Masonry,SnapKit.

1.IB方式实现自动布局

  • 在xib或者stroyBoard中添加约束的核心思想是一定要确定视图的frame,不管是设置(Leading,Top,Width,Height),还是设置距离上下左右的间距都是为了确定视图的farme,根据需求做不同的设置.如果是设置上下左右的间距固定,则视图的尺寸会不同尺寸的手机显示的大小不同,如果设置的是Width,Height以及Leading(或者Trailing),Top(或者Bottom),则实体的长宽固定,而没有添加约束的两条边的边距根据手机的尺寸不同而不同.总至要至少要添加四条约束.

  • 但是有些控件添加两条约束Leading(或者Trailing),Top(或者Bottom)也是可以的,如UILabel,UIimageView,UIButton,如果不设置宽高这些控件会根据内容自动调整大小.IB自动布局大家都比较熟悉,这里就不再详述.

2.VFL/NSLayoutConstrais方式实现自动布局

  • VFL(Virsual Format Language)是一种虚拟的格式化语言,主要用来创建AutoLayout的约束字符串。
  • VFL官网介绍
  • VFL基本语法介绍:例如: "V:[view1]-0-[view2(==100@1000)]-<=0-|"
功能 表达式
垂直方向 V:
水平方向 H:
Views [view1],[View2]
SuperView ` `
关系(不写默认==) >=,==,<=
空间,间隙(不写默认值8) -
优先级 @value
  • 示例:添加上中下三个视图,上中视图等高,下视图距中间视图底部的间隙8,并且占满屏幕剩余部分.
        let topView = UIView()
        topView.backgroundColor = UIColor.red
        topView.translatesAutoresizingMaskIntoConstraints = false
        
        let middleView = UIView()
        middleView.backgroundColor = UIColor.green
        middleView.translatesAutoresizingMaskIntoConstraints = false
    
        
        let bottomView = UIView()
        bottomView.backgroundColor = UIColor.blue
        bottomView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(topView)
        view.addSubview(middleView)
        view.addSubview(bottomView)
        /*
         oc 中 NSDictionaryOfVariableBindings(v1,v2,v3)相当于[NSDictionary dictionaryWithObjectsAndKeys:v1,@“v1”,v2,@“v2”,v3,@“v3”,nil]
         */
    
        //水平约束,当为==时==可以省略
        let topViewHorizontalContriants = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[topView]-==0-|", options:[], metrics: nil, views: ["topView":topView])
        view.addConstraints(topViewHorizontalContriants)
        //垂直约束
        let topViewVContriants = NSLayoutConstraint.constraints(withVisualFormat: "V:|-64-[topView(==100@1000)]", options: [], metrics: nil, views: ["topView":topView])
        view.addConstraints(topViewVContriants)
        
        //middleView的顶部和topView的底部对齐
        let middleTopC = NSLayoutConstraint.init(item: middleView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: topView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 0)
        //middleView的左边和topView的左边对齐对齐
        view.addConstraint(middleTopC)
        let middleleftC = NSLayoutConstraint.init(item: middleView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: topView, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1.0, constant: 0)
         view.addConstraint(middleleftC)
        //middleView的宽等于topView的宽度
        let middleWidthC = NSLayoutConstraint.init(item: middleView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: topView, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1.0, constant: 0)
         view.addConstraint(middleWidthC)
        
        //middleView的高度等于topView的高度
        let middleHeigitC = NSLayoutConstraint.init(item: middleView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: topView, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 0)
        view.addConstraint(middleHeigitC)
        //bottomView的垂直方向上的约束,不写间距默认是8
        let bottomVC = NSLayoutConstraint.constraints(withVisualFormat: "V:[middleView]-[bottomView]-0-|", options: [], metrics: nil, views: ["middleView":middleView,"bottomView":bottomView])
        view .addConstraints(bottomVC)
        //水平约束
        let bottomHC = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[bottomView]-0-|", options: [], metrics: nil, views: ["bottomView":bottomView])
        view.addConstraints(bottomHC)
iOS AutoLayout_第1张图片
view添加子视图.png
  • 给下面蓝色视图添加一个UILabel,使其上左右边距为10,高度自适应
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 15)
        label.numberOfLines = 0;
        label.backgroundColor = UIColor.cyan
        bottomView.addSubview(label)

        //水平约束
        let labelHC = NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[label]-10-|", options: [], metrics: nil, views: ["label":label])
        bottomView.addConstraints(labelHC)
        //垂直约束,顶部对齐,高度自适应
        let labelVC = NSLayoutConstraint.constraints(withVisualFormat: "V:|-10-[label]", options: [], metrics: nil, views: ["label":label])
        bottomView.addConstraints(labelVC)
        label.text = "lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世vlalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三lalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世vlalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三lalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世vlalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三lalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世vlalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三lalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世vlalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三lalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世lalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世vlalalalalaz汽车消费网lalalalalaz汽车消费网lalalalalaz三生三世"
        

        print(bottomView.constraints)
//        label.frame.size = CGSize(width: 10, height: 10)
        UIView.animate(withDuration: 3) {
//            self.label.setNeedsUpdateConstraints()
            self.label.layoutIfNeeded()
        }
        
iOS AutoLayout_第2张图片
blueView添加label.png
  • 删除约束,更新布局

extension ViewController {
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        let labelHC1 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-60-[label]-60-|", options: [], metrics: nil, views: ["label":label])
        bottomView.removeConstraints(labelHC!)
        bottomView.addConstraints(labelHC1)

        UIView.animate(withDuration: 3) {
//            self.bottomView.setNeedsUpdateConstraints()
            self.bottomView.layoutIfNeeded()
//            self.bottomView.updateConstraints()
//            self.bottomView.updateConstraintsIfNeeded()
        }
    }
}
更新约束.gif

小结

  • 上面可以看出:NSLayoutConstraintconstraints方法是通过VFL添加水平和垂直方向约束来进行自动布局,而NSLayoutConstraint的便利构造方法是通过添加上下左右间距或间距和大小来添加约束来自动布局,两者单独使用或结合使用都可以,各有优缺点,一般会结合使用.

/*
withVisualFormat : VFL语句
options : 字典类型的值;这里的值一般在系统定义的一个enum里面选取,默认传空的字典
metrics : 一般为nil ,参数类型为NSDictionary,从外部传入 //衡量标准
views :  就是上面所加入到NSDictionary中的绑定的View,要求字典形式为 ["view":view,"label":label]
*/
open class func constraints(withVisualFormat format: String, options opts: NSLayoutConstraint.FormatOptions = [], metrics: [String : Any]?, views: [String : Any]) -> [NSLayoutConstraint]

/*
item : 指定需要添加约束的视图
attr1 : 指定视图一需要约束的属性
relatedBy : 指定视图一和视图二添加约束的关系(lessThanOrEqual 小于等于,equal 等于,greaterThanOrEqual 大于等于)
toItem : 指定视图一依赖关系的视图二;可为nil
attr2 : 指定视图一所依赖的视图二的属性,若view2=nil,该属性设置 NSLayoutAttributeNotAnAttribute
multiplier : 系数,情况一:设置A视图的高度 = A视图高度 * multiplier + constant;此时才会起作用;情况二:设置A视图和其他视图的关系或 toItem=nil,multiplier设置不等于0即可,若等于0会crash;       
constant :常量,间距的值
*/

public convenience init(item view1: Any, attribute attr1: NSLayoutConstraint.Attribute, relatedBy relation: NSLayoutConstraint.Relation, toItem view2: Any?, attribute attr2: NSLayoutConstraint.Attribute, multiplier: CGFloat, constant c: CGFloat)
  • oc 中 NSDictionaryOfVariableBindings(v1,v2,v3)宏相当于[NSDictionary dictionaryWithObjectsAndKeys:v1,@“v1”,v2,@“v2”,v3,@“v3”,nil]

  • 父控件是谁,这些约束就添加给谁.

  • UIViewopen var translatesAutoresizingMaskIntoConstraints: Bool属性,把 autoresizingMask 转换为 Constraints,即:可以把 frame ,bouds,center 方式布局的视图自动转化为约束形式。(此时该视图上约束已经足够 不需要手动去添加别的约束).

  • 用代码创建的所有view , translatesAutoresizingMaskIntoConstraints 默认是 true,用 IB 创建的所有 view ,translatesAutoresizingMaskIntoConstraints 默认是 false . 如果用代码约束布局,则需要将该属性设置为false.

  • translatesAutoresizingMaskIntoConstraints属性autoresize布局:true,autolayout布局 :false`.

  • 在更新约束时,要先删除一些约束removeConstraints,再添加对应的一些约束addConstraints,然后跟新约束才有有效.

  • 项目开发过程中,通常使用的是Masonry\SnapKit自动布局的三方库,简单高效。较之frame布局,自动布局更适合业务拓展。

基于约束的AutoLayer更新约束的方法

1、setNeedsUpdateConstraints
当一个自定义view的某个属性发生改变,并且可能影响到constraint时,需要调用此方法去标记constraints需要在未来的某个点更新,系统然后调用updateConstraints.
2、needsUpdateConstraints
constraint-based layout system使用此返回值去决定是否需要调用updateConstraints作为正常布局过程的一部分。
3、updateConstraintsIfNeeded
立即触发约束更新,自动更新布局。
4、updateConstraints
自定义view应该重写此方法在其中建立constraints. 注意:要在实现在最后调用[super updateConstraints]
uto Layout Process 自动布局过程
与使用springs and struts(autoresizingMask)比较,Auto layout在view显示之前,多引入了两个步骤:updating constraints 和laying out views。每一个步骤都依赖于上一个。display依赖layout,而layout依赖updating constraints。 updating constraints->layout->display
第一步:updating constraints,被称为测量阶段,其从下向上(from subview to super view),为下一步layout准备信息。可以通过调用方法setNeedUpdateConstraints去触发此步。constraints的改变也会自动的触发此步。但是,当你自定义view的时候,如果一些改变可能会影响到布局的时候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。
自定义view的话,通常可以重写updateConstraints方法,在其中可以添加view需要的局部的contraints。
第二步:layout,其从上向下(from super view to subview),此步主要应用上一步的信息去设置view的center和bounds。可以通过调用setNeedsLayout去触发此步骤,此方法不会立即应用layout。如果想要系统立即的更新layout,可以调用layoutIfNeeded。另外,自定义view可以重写方法layoutSubViews来在layout的工程中得到更多的定制化效果。
第三步:display,此步时把view渲染到屏幕上,它与你是否使用Auto layout无关,其操作是从上向下(from super view to subview),通过调用setNeedsDisplay触发,
因为每一步都依赖前一步,因此一个display可能会触发layout,当有任何layout没有被处理的时候,同理,layout可能会触发updating constraints,当constraint system更新改变的时候。
需要注意的是,这三步不是单向的,constraint-based layout是一个迭代的过程,layout过程中,可能去改变constraints,有一次触发updating constraints,进行一轮layout过程。
注意:如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。

相关参考

  • VFL的简单使用教程

  • iOS-手动添加限制-constraintsWithVisualForm

  • translatesAutoresizingMaskIntoConstraints 详解

  • 自动布局之-NSLayoutConstraint

  • Autoresize和Autolayout 布局的实现

  • iOS AutoLayout全解

  • setNeedsUpdateConstraints,needsUpdateConstraints等基于约束的AutoLayer的方法

  • ios – setNeedsLayout vs. setNeedsUpdateConstraints and layoutIfNeeded vs updateConstraintsIfNeeded

  • MY_updateConstraintsIfNeeded,setNeedsLayout, layoutIfNeeded 和 layoutSubviews 方法之间的关系解释

  • iOS自动约束】四个重要的系统方法

  • UIStackView

  • Stack

  • 相关源码(包括AutoLayout,Masonry,SnapKit):AutoLayoutStudy

你可能感兴趣的:(iOS AutoLayout)