iOS 自动布局(AutoLayout)——原理

配图.jpg

正常情况写布局的两种方式

一、手写布局

就是直接设置view的frame属性,不详细说明了

二、自动布局

自动布局Auto Layout,苹果为我们提供了一整套布局系统(layout Engine),这套系统会将视图、约束、优先级、大小通过计算转换成对应的frame,而且当约束改变的时候,会再次触发该系统重新计算。整个过程如下图:(图片出自WWDC2015 地址)

image.png
  • Constraints Change

激活,失活
创建约束,优先级
添加,移除视图

检测到改变后系统(Layout Engine) 会重新计算出布局,就会调用superview.setNeedsLayout()

  • Deferred Layout Pass

容错处理
从上往下调用layoutSubviews()
从 Layout Engine 拷贝出子视图 frame

到此,系统就计算好frame了,接下来就是渲染了,和手写布局是一样的

  • Application Run Loop

Layout Engine 通过系统Run Loop 循环cycle

整个流程可以理解为,当修改约束的时候,会触发Layout Engine去计算出view的frame,然后从上而下布局,最后在下一个运行循环中更新界面

整个布局流程可以分为三个阶段:

计算frame,布局,渲染

1.计算阶段

- (void)updateConstraints;

用xib或者NSLayoutConstraint自动布局都会调用该方法,是通过手布局(创建view,设置frame)不会调用此方法

补充:基于约束的布局是懒加载触发的,所以只有设置了约束系统才会调用updateConstraints,如果把基于frame的布局写到updateConstraints,系统是不知道你的布局方式,通过重写下面这个方法,返回YES,系统就会调用updateConstraints

+ (BOOL)requiresConstraintBasedLayout{
    return YES;
}

布局可以写在updateConstraints中,并且必须调用 [super updateViewConstraints]
苹果不建议把初始化的约束写在这个方法里,原因如下:
(1) 当视图约束被更新的时候(一般是被setNeedsUpdateConstraints标记更新) updateConstraints这个方法会被调用,如果里面包含大量的约束,系统就需要去判断是否已经存在相同的约束,
(2) 当前view不一定拥有所有的约束,其他view可能已经向该view添加了部分约束
(3) 如果在点击事件中触发修改约束的行为,修改布局的代码和触发更新的代码不再同一处,这会让逻辑变得难以遵循

仅需要更新约束的这部分代码写到updateViewConstraints,大量的初始化约束写到类似于 init,viewDidLoad中.

- (void)updateViewConstraints{
    [self.subView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view).offset(100);
        make.left.equalTo(self.view).offset(0);
        make.right.equalTo(self.view).offset(-100);
        make.bottom.equalTo(self.view).offset(-100);
    }];
    [super updateViewConstraints];
}

此外还会通过以下方式触发系统调用: updateConstraints

  • setNeedsUpdateConstraints 标记更新约束,会在下次 Cycle中自动调用updateConstraints,
[self.subView setNeedsUpdateConstraints];
ViewDemo[19068:895608] -[subView updateConstraints]
  • updateConstraintsIfNeeded 如果有被setNeedsUpdateConstraints标记的更新,立即在当前Cycle调用updateConstraints
[self.subView updateConstraintsIfNeeded];

2.布局阶段

- (void)layoutSubviews;

此方法由系统调用,被调用时,系统已经计算好view对应的frame,如果需要修改布局,通过重写这个方法,并在方法体里修改frame,但是在方法里需要注意
(1) 在方法里必须调用 super.layoutSubviews()
(2) 不能在方法里修改约束,修改约束会触发系统重新计算布局,可能会导致布局错乱
(3) 不能在方法里调用setNeedsUpdateConstraints(),setNeedsLayout如,可能会导致布局错乱

除此之外,还会通过以下方式触发:

  • 第一次addSubview的时候,改变frame的时候(view已经addSubview 且两次变化值不能一样)
UIView *subView = [[UIView alloc] init];
[self.view addSubview:subView];
  • setNeedsLayout,标记更新布局,调用此方法标记,会在下一次runloop来到layoutSubviews,上文的 Layout Engine 也会在检测到约束变化后,通过super. setNeedsLayout()标记,最后从上往下调用layoutSubviews()
[self.subView setNeedsLayout];
  • layoutIfNeeded,如果有标记的更新,立即在当前Drawing Cycle调用layoutSubviews更新视图
 [self.subView layoutIfNeeded];

3.渲染阶段

- (void)drawRect:(CGRect)rect;

当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用。-drawRect:方法里面的代码利用Core Graphics去绘制一个寄宿图,然后内容就会被缓存起来直到它需要被更新

- (void)setNeedsDisplay,标记需要显示,在下个drawing cycle 调用

在UIViewController中

- (void)updateViewConstraints;

控制器的view.updateConstraints()方法调用时,对应控制器的updateViewConstraints就会被调用,控制器view的子view约束改变是不会触发的.

- (void)viewWillLayoutSubviews

当视图控制器的视图的边界发生变化时,该视图将调整其子视图的位置,然后系统调用此方法。但是,调用此方法并不表示该视图的子视图的各个布局已调整。每个子视图负责调整其自己的布局。
视图布局子视图后,视图控制器可以重写此方法以进行更改。此方法的默认实现不执行任何操作。

- (void)viewDidLayoutSubviews

当视图控制器的视图的边界发生变化时,视图将调整其子视图的位置,然后系统调用此方法。但是,调用此方法并不表示该视图的子视图的各个布局已调整。每个子视图负责调整其自己的布局。
视图布局子视图后,视图控制器可以重写此方法以进行更改。此方法的默认实现不执行任何操作。

三个方法在控制器view的子view约束改变时是不会触发的.

在viewController中这三个方法是系统为我们提供的便利,方便我们在控制器自带的view发生变化的时候做相应的操作:

updateViewConstraints->viewWillLayoutSubviews->viewDidLayoutSubviews

总结一下:

在同一代码处即有自动布局又有手动布局

  • 从以上分析得出,自动布局会触发Layout Engine 去计算view的frame,最后重新赋值给view,所以手写的布局(直接设置frame)一般是没有任何效果的;

获取,修改view.frame

  • 自动布局系统在计算出view的frame以后,会自上而下(父视图-子视图)调用layoutSubviews,要获取期望的frame,因该在layoutSubviews里获取,修改frame

translatesAutoresizingMaskIntoConstraints

  • 设置为YES,系统会把autoresizingMask 转换为 Constraints,通常我们给代码添加自动布局,记得把该属性设置为NO,这样会避免出现布局不一致

在xib中,如果设置了auto layout 该属性默认是NO
在手写代码中,该属性默认是YES

  • 初始化约束,尽量写在init,viewDidLoad中,修改.删除约束,可以直接写在对应的事件中,如果对性能有考虑,写在updateConstraints()以获得最佳性能。

  • 不要手动调用layoutSubviews, updateConstraints,这些方法系统会自己触发,若要更新约束,布局,通过setNeedsUpdateConstraints,setNeedsLayout标记更新

cafe-3537801_1280.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355