iOS页面的布局方式

iOS有三种基本的界面布局的方法,分别是手写UI,xib和storyboard。手写UI是最早进行UI界面布局的方法,优点是灵活自由,缺点是需要写大段的代码进行布局。xib也是比较早出现的UI布局的方式,优点是不需要手写代码,但是每个界面对应一个xib,管理起来复杂。而storyboard则是在iOS5以后出现的,是苹果官方主推的一个代替xib的策略,不仅能将xib汇总统一管理,还可以描述各种场景之间的过渡,缺点是多人协作开发时容易产生冲突。

下面主要介绍的是手写页面布局。

一、AutoresizingMasks

可以使用 AutoresizingMasks 进行页面布局,在 UIView 中有一个autoresizingMask的属性,它对应的是一个枚举的值,属性的意思就是自动调整子控件与父控件中间的位置,宽高。默认值是UIViewAutoresizingNone,控件不会随父视图的改变而改变。

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
     UIViewAutoresizingNone = 0,
     UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 自动调整view与父视图左边距,以保证右边距不变
     UIViewAutoresizingFlexibleWidth = 1 << 1, // 自动调整view的宽度,保证左边距和右边距不变
     UIViewAutoresizingFlexibleRightMargin = 1 << 2, // 自动调整view与父视图右边距,以保证左边距不变
     UIViewAutoresizingFlexibleTopMargin = 1 << 3, // 自动调整view与父视图上边距,以保证下边距不变
     UIViewAutoresizingFlexibleHeight = 1 << 4, // 自动调整view的高度,以保证上边距和下边距不变
     UIViewAutoresizingFlexibleBottomMargin = 1 << 5 // 自动调整view与父视图的下边距,以保证上边距不变
}

AutoresizingMasks是对未来变化的一种预期,系统会生成frame的布局,当遇到需要使用到多个值的场景时,支持使用|操作符。
例如,需要设置播放器浮层随播放器大小变化:

UIView *overlay = [[UIView alloc] init];
overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:overlay];

Autoresizing需要注意的是,storyboard中设置的约束和手写代码中设置的约束是相反的。storyboard 图形页面里点的右边的线和下边的线的意思是“固定”。

二、Frame

frame指的是当前视图在其父视图中的位置和大小。
在初始化 view 的时候,可以设置 view 的frame

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)]; 

初始化一个距离父视图左边距10,上边距20,宽30,高40的视图。也可以修改声明View的位置以及大小。

view.frame = CGRectMake(20, 10, 40, 30);

设置和修改视图的 frame 可以完成对界面的布局。

2.1 bounds

提到 frame 不得不提 bounds,bounds指的是前视图在其自身坐标系统中的位置和大小。可以看到两者的区别在于坐标系不同。

2.2 layoutSubviews

需要重新布局视图可以使用 layoutSubviews

1)可以在view里重写layoutSubviews

2)可以在view controller里使用

viewWillLayoutSubviews 在autoresizingMasks前调用

viewDidLayoutSubviews 在autoresizingMasks后调用,肯定会覆盖autoresizingMasks的结果

layoutSubviews可能会在不需要调用的时候调用,如果layoutSubviews的比较复杂,可能会卡顿

三、自动布局AutoLayout

前面讲到的 frame 主要用于视图的绝对位置,但是 iOS 设备有多个尺寸,如何对不同尺寸进行适应,苹果的解决方案是使用 AutoLayout。

如果是从代码层面开始使用 Autolayout,需要对使用的 View 的translatesAutoresizingMaskIntoConstraints 的属性设置为NO。即可开始通过代码添加Constraint,否则View还是会按照以往的autoresizingMask进行计算。而在 Interface Builder 中勾选了Use Auto layout,translatesAutoresizingMaskIntoConstraints 属性都会被默认设置NO。

3.1 约束

自动布局里最重要的组成部分就是约束。分别可以设置视图相对于另一个视图的 leading、trailing、top、bottom、CenterX、CenterY 等关系。根据这些约束来确定视图的相对位置。

视图的约束之间的关系为线型关系。例如,视图Y 相对于 视图X 的位置可以表示为一个线性变换,即

Y = kX + b

即 Y 是 X 某个方向坐标或大小的 k 倍并偏移 b。k 和 b 的大小可以是0。如果 k = 1, b = 0, 则表示 Y 和 X 分别表示视图的宽,则等式表示 Y 和 X 的宽度相等。

AutoLayout 的核心是:Every view requires at least two constraints along each axis to set position and size. 即在每个坐标轴上至少需要2个约束来确定视图位置。

3.2 Ambiguous Layout

在开发过程中,你可以通过调用hasAmbiguousLayout 来测试你的view约束是否足够的。函数会返回布尔值。如果有一个不同的frame就会返回yes,如果view的约束完全指定了就会返回no。
一个设定了完全约束的view的子view也可能存在ambiguous layout,需要为每一个view单独测试layout是否存在ambiguous layout。

3.3 Intrinsic Content Size

使用autolayout时,view的content扮演着非常重要的角色。每个view的intrinsicContentSize描述了不会剪切的显示完整view content的最小空间。例如一个image view,content size根据image显示的size设置。一个大的image需要一个大的固有的content size。image的大小提供给了view。
对于button,固有的content size根据他的title而有不同。随着title增长或者缩短,button的固有的content size也会调节来做适应,可以根据你自定义的font size和title text而有变化。

3.4 Compression Resistance and Content Hugging

3.4.1 compression resistance

压缩阻力表示一个视图的抗压缩性。一个有高compression resistance的视图会防止被压缩。也不会允许content被裁剪,而会尝试保存他的最小固有content size。
autolayout经常遇到两个冲突的请求。当只有一个请求会成功时,他就会满足高优先级的那个。可以分别设置水平和垂直方向的Compression Resistance。value从1(最低)到1,000(请求的优先级)不等。默认的是750。

[button setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal]; 

3.4.2 content hugging

抗拉属性表示view防止被拉伸的属性,和压缩阻力类似。默认值为250。

[button setContentHuggingPriority:501 forAxis:UILayoutConstraintAxisHorizontal];

3.5 VFL

Visual Format Language,即“可视化格式语言”。直接手写约束很复杂,使用VFL相对简单很多,但比较难进行调试。

  [self.view addConstraints: [NSLayoutConstraint            
constraintsWithVisualFormat:@"V:[view1]-8-[view2]"             
                    options:NSLayoutFormatAlignAllLeading
                    metrics:nil
                      views:NSDictionaryOfVariableBindings(view1, view2)]];

3.6 Masonry

VFL的写法也相当复杂,可以使用第三方框架 Masonry,Masonry 是一个轻量级的布局框架,Masonry 源码:https://github.com/Masonry/Masonry

例如,设置view1相对父View的每个边距离为padding:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

需要注意的是,在结构一样的情况下用mas_updateConstraints,会更新当前的约束,但是如果要覆盖缘由约束重新添加,则需要使用方法用mas_remakeConstraints

constraint加到两个view的公共父view上,因此有一个奇怪的现象是一个view不持有自己的约束,而被其他view持有。在 Masonry 中,实际添加constraint的不一定是约束的持有者

四、更新布局方法

设置好约束以后,布局是如何更新的呢?

Constraints

- (void)updateConstraintsIfNeeded    // 立即重新计算约束,如果在这之前addConstraints,就可以更新约束
- (void)setNeedsUpdateConstraints   // 立即返回,标记说需要改变约束值,在当前update cycle结束后更新之前所有标记过要改变的约束,调用updateConstraints方法

Layout

- (void)layoutIfNeeded     // 立即更新布局,重新计算约束,如果在这之前addConstraints就会立即反应在页面上
- (void)setNeedsLayout    // 同Constraints,不过是更新布局
- (void)layoutSubviews    // 布局当前页面的子页面

Draw

- (void)setNeedsDisplay   // 同Constraints,不过是重新渲染

4.1 Constraints,Layout,Draw调用顺序

一个页面更新的顺序一般为
调用约束计算出frame(Constraints)→ 根据计算出的frame重新布局(Layout) → 根据重新布局的结果进行图像渲染(Draw)

layout的改变会导致重新计算Constraints
layoutIfNeeded会调用updateConstraintsIfNeeded

Constraints的计算顺序是低到上 (从subview到superview)
layout的更新顺序是从顶到下(从superview到subview)

4.2 frame和约束区别

1、frame是不可以累加的,只能被替换掉,但是constraint可以累加

2、frame不可以跨级添加,但是contraint可以跨层级

3、contraint可以设定priority

另外,需要注意的是,在autolayout下使用frame,会把frame转化成autolayout的约束,如果再进行约束的设置,由于多次累加可能会造成冲突

github博客:https://wf96390.github.io/blog/2016/03/16/autolayout/

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

推荐阅读更多精彩内容