0. 较好的相关网站

各种设备的屏幕尺寸查询
Paint Code屏幕尺寸查询

1. 目录
  • iOS界面开发中常用的概念
  • iOS坐标系统及坐标转换
  • iOS布局相关函数
  • AutoresizingMask,AutoLayout,frame 布局
  • 三方开源布局引擎对比
  • iOS界面适配
2. 开篇叨叨

iPhone刚推出的时候尺寸比较单一,只有3.5英寸,这时候用frame就可以轻松搞定布局,后面推出了4.0英寸,4.7英寸,5.5英寸,5.8英寸,6.1英寸,6.5英寸,各种尺寸的屏幕,这给界面适配带来了很大困难,后面引入的刘海屏等更加剧了界面适配的难度。下图是iPhone和iPad不同机型的尺寸数据:



为了解决这个问题苹果公司推出了 AutoresizingMask,用来协调子视图与父视图之间的关系。而后又相继推出了AutoLayout供开发者进行屏幕适配,目前用得比较多的就是AutoLayout和frame布局,但是AutoLayout其实质是用一个个约束条件来约束布局参数,这些约束条件会被转换成一个不等式组,布局视图的阶段,其实就是解方程组的阶段,约束越多性能越低,对于十分复杂的界面是十分不适用的,因此一般大型项目都会回归到基于frame的布局,当然不会是手动计算,有很多开源的布局引擎比如YogaKit,比如LayoutKit都将针对frame的布局形式简化成十分易用,并且性能优于AutoLayout的布局形式,这篇博客将从界面开发的基础概念出发,介绍界面的组成,坐标系统,布局相关函数,原生三大布局方式,三方开源布局引擎以及iOS适配的方方面面。

后续还会针对个人正在使用的基于frame的开源布局引擎YogaKit以及基于AutoLayout的Masonry布局引擎进行源码分析。

3. 正文
3.1 iOS界面开发中常用的概念
3.2 点(Point) 屏幕像素 (Pixel) 对角线长度 ,屏幕密度,屏幕模式,宽高比

说到点和屏幕像素就会扯到逻辑像素和物理像素这两个概念,逻辑像素的单位是pt,它是按照内容的尺寸计算的单位,也就是这里所说的点,而物理像素就是这里的屏幕像素(Pixel) 。如果使用Sketch或Adobe XD设计界面,那么只要使用pt作为单位设计出一套就可以了,我们在开发的时候也只需要以pt为单位的进行开发。而屏幕像素一般是用来结合对角线长度来描述整个屏幕的ppi(每英寸的像素数),用于描述屏幕的细腻程度。计算方式如下

V(长像素数)*(长像素数) + (宽像素数) * (宽像素数) = 对角线像素数  (V表示开根号)

PPI = 对角线像素数/对角线长度

屏幕模式用于匹配App素材。为适配不同的屏幕,iOS App 的同一个图标,往往会准备 1x、2x、3x 等几个图片素材。假如屏幕 scale = 3,就会优先选取 3x 素材;假如屏幕 scale = 2, 就优先选择 2x 素材。scale也决定着点和像素之间的关系。当屏幕模式为 1x, 一个点就等于 1 个像素,当屏幕模式为 2x,一个点就等于 2 个像素。也就是说:

pixel = point * scale

宽高比则是在视频图片适配的时候比较重要,比如在手机上显示同一个视频,视频等比例铺满高度,在 iPhone X 左右两边就被裁掉更多内容。而视频等比例适应宽度,iPhone X 上下就被留更多的黑边。

3.3 布局坐标系及关键的属性

在iOS中有两大比较典型的坐标系,一个是UIKit的坐标系,零点位于左上角,一个是Core Graphic坐标系,零点位于左下角。
在布局使用的坐标系为UIKit坐标系,也就是说原点位于左上角。

UIView 有几个比较重要的布局属性:frame, bounds, position,center,anchorPoint

* frame 表示视图,图层的外部坐标,也就是当前UIView相对于父视图的坐标
* bounds 表示视图,图层的内部坐标。原点位于左上角,它的作用主要是用于存放宽高尺寸。
* center 和 position 代表相对于父图层anchorPoint所在的位置,center 是视图的称呼,postion是图层里面的称呼,二者是同一个值。
* anchorPoint 可以看成是一个UIView的移动支点。我们设置center或者position的时候就是移动anchorPoint坐标,anchorPoint用单位坐标来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}。anchorPoint可以通过指定x和y值小于0或者大于1,使它放置在图层范围之外。

frame是一个关联属性,是根据bounds,center和transform计算出来的,后面的任何一个值变化都会影响到frame的值。而frame值一旦改变也会对bounds,center和transform产生影响。同时也需要注意的是当图层做变换后,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域。这时候frame宽高和bounds有可能不一样了。

3.4 iOS布局相关函数

为什么需要理解这块,因为刚接触iOS开发的时候最耗时的还是调试界面布局上面,这主要是对view 的 frame何时真正更新不是很清楚。该小结争取解开这个环节中的各个疑惑。

首先我们在触摸屏幕的时候会产生一系列的事件,这些事件由系统进行分发并传递到应用中,并通过一定的回调调用我们相应事件的响应方法,在响应方法中我们可能会修改当前界面上某个View,这时候系统并不会直接对view进行修改,重绘,因为有可能还会有其他事件需要相应。这时候只是将这些view标记为脏视图,当这些方法调用返回后,控制流回到主Runloop中,在Runloop进入休眠之前,会先进入Update cycle,在 Update cycle 中完成布局并重新渲染整个界面,由于iOS设备会以每秒60帧的速度刷新界面,这个时间很短用户在和应用交互时几乎感觉不到刷新UI时候带来的更新延迟,在整个过程系统会调用如下几个关键函数,这里会先介绍各个函数的功能,然后看下这些函数是怎么结合起来的。

3.4.1 frame布局
3.4.1.1 自动触发布局

在某些节点系统会帮我们自动给视图打上需要布局的标记,在下一个Update cycle的时候layoutSubviews会被调用,而不需要手动触发:


* 通过addSubView 新增子view的时候
* 设置self.view及子视图的frame.size会触发layoutSubviews,当然前提是frame.size的值设置前后发生了变化,注意,此处不是origin
* 滚动UIScrollView的时候会触发
* 横竖屏幕切换会触发
* 更新视图的 constraints

3.4.1.2 手动触发布局
  1. setNeedsLayout

在事件响应函数中我们可能会提交不是需要非常实时呈现在界面上的事件,这种情况下就可以调用这个方法,告诉系统该视图需要重新进行布局计算,也就是将当前视图标记为脏数据。这个方法会立刻执行并返回,但是在返回后并不会立即更新View.它会在下一个Update cycle中调用layoutSubviews集中对脏数据进行更新,这就导致了setNeedsLayout返回后到视图被重新绘制并布局之间有一段任意时间的间隔,但是这个延迟不会对用户造成影响,反而能将关键的时间留给其他事件的响应,这是一个比较正常的情景。

  1. layoutIfNeeded

layoutIfNeeded和setNeedsLayout一样也会触发layoutSubviews,但是这两个方法是有区别的:调用setNeedsLayout()并不会立刻触发layoutSubviews函数而是会延迟到Upate cycle中触发。而layoutIfNeeded会立刻触发,但是这里需要注意的是如果调用了layoutIfNeeded之前,并没有调用setNeedsLayout将视图标记为脏视图,那么就不会调用 layoutsubview。如果你在同一个 run loop 内调用两次 layoutIfNeeded,并且两次之间没有更新视图,第二个调用同样不会触发 layoutSubviews 方法,也就是说layoutIfNeeded必须结合setNeedsLayout使用。

3.4.1.3 布局回调节点

1. layoutSubviews

这个方法会在需要对界面进行重新定位和大小调整的时候被调用,它负责给出当前view和每个子view的位置和大小。这个方法十分耗时因为它会在每个子视图上起作用并且调用它们相应的layoutSubviews方法,所以我们可以在需要重新定位或者更改大小的情形下重载它,但是这个方法不能直接调用。但是可以通过上面介绍的setNeedsLayout方法以及layoutIfNeeded方法进行触发,如果不是非常必要可以通过调用setNeedsLayout后将这个方法的调用时机放在Update Cycle 中。这样会比调用setNeedsLayout立刻调用layoutIfNeeded这种方案消耗的资源要小得多。
这里需要注意的是layoutSubviews会先对父视图进行layoutSubviews,完成后再调用子视图的layoutSubviews

总结下layoutSubviews的触发点:

1、init初始化不会触发layoutSubviews,但是用initWithFrame进行初始化的时候,当rect的值不是CGRectZero时也会触发
2、addSubview会触发layoutSubviews。
3、设置view的frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转屏幕会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
7、直接调用setLayoutSubviews。

2. viewWillLayoutSubviews

viewWillLayoutSubviews/viewDidLayoutSubviews 是UIViewController中的方法,它会在ViewController的根view的layoutSubviews被调用之前被触发。

3. viewDidLayoutSubviews

viewDidLayoutSubviews会在ViewController的根view的layoutSubviews被调用之后被触发,在这个节点上viewController中的根view以及子view的尺寸都已经确定了,所以我们可以把所有依赖于布局或者大小的代码放在 viewDidLayoutSubviews 中,而不是放在 viewDidLoad 或者 viewDidAppear 中。

  • sizeThatFits

sizeThatFits有两种功能:

  1. 给一个限定的宽度和高度让view在这个范围内进行自适应size
  2. 在某个View中重写sizeThatFits,并返回需要的尺寸
  • sizeToFit

当调用UIView的sizeToFit后会调用sizeThatFits方法来计算UIView的bounds.size然后改变frame.size,注意 Autolayout 约束过的 view 该方法失效

- (CGSize)sizeThatFits:(CGSize)size {

CGFloat w = size.width;
w -= 2 * _margin;
_cacheSize1 = [_label1 sizeThatFits:CGSizeMake(w, MAXFLOAT)];
_cacheSize2 = [_label2 sizeThatFits:CGSizeMake(w, MAXFLOAT)];
CGFloat h = 3 * _margin + _cacheSize1.height + _cacheSize2.height;
return CGSizeMake(size.width, h);
}

IDLCustomView *customerView = [[IDLCustomView alloc] init];
......

[customerView sizeToFit];
[self.view addSubview:customerView];

3.4.2 自动布局

自动布局相对于frame布局来说多出了一步–约束计算,整个自动布局包括如下三个阶段:

1. 约束计算: 从子View->父View,系统会计算并给视图设置所有要求的约束,更新约束或者手动调用setNeedsUpdateConstraints都会触发这个过程。
2. 布局阶段: 从父View->子View,布局引擎计算并设置视图和子视图的frame,这个阶段也可以通过手动和自动方式触发,上面已经做过详细的介绍了。
3. 显示阶段: 从父View->子View,重绘视图的内容,如果实现了draw 方法则会被调用。

1. updateConstraints

这个方法绝不能显式调用,而应该被重载,需要注意的是在updateConstraints方法中只做必须要更新的约束。约束的初始化工作一般在视图的初始化方法或者 viewDidLoad() 方法中指定,通常情况下,开启或者关闭 constrains、更改 constrain 的属性或者从视图层级中移除一个视图时都会设置一个内部的标记,这个标记会在下一个更新周期中触发updateConstrains。当然和布局一样也可以手动触发updateConstrains,比如setNeedsUpdateConstraints,updateConstraintsIfNeeded。

2. setNeedsUpdateConstraints

setNeedsUpdateConstraints和setNeedsLayout一样会保证在下一次更新周期中更新约束。

3. updateConstraintsIfNeeded

updateConstraintsIfNeeded 和 layoutIfNeeded一样会立即触发updateConstraints(),而不会等到主线程Runloop进入休眠前调用。

4. invalidateIntrinsicContentSize

自动布局中某些视图拥有intrinsicContentSize属性,这是视图根据它的内容得到的自然尺寸,它是由所包含的元素的约束决定,但也可以通过重载提供自定义行为。我们可以调用invalidateIntrinsicContentSize()方法设置一个标记表示这个视图的intrinsicContentSize 已经过期,需要在下一个布局阶段重新计算。

3.4.3 显示

1. setNeedsDisplay

类似于setNeedsLayout,setNeedsUpdateConstraints,它会将需要重绘的视图标记为脏视图,但是不会立刻调用而是在下一个Update Cycle 中遍历所有已标标记的视图,并调用它们的draw 方法,如果只想重绘部分视图,可以调用 setNeedsDisplayInRect,并把需要重绘的矩形部分传进去。

2. drawRect

UIView的drawRect方法类似于视图布局的layoutSubviews是重绘视图的节点,但是不同于layoutSubviews,drawRect方法不会触发后续对视图的子视图方法的调用,视图的显示方法里没有类似布局中的layoutIfNeeded 这样可以触发立即更新的方法。

还需要注意的是layoutSubviews方法会先于drawRect调用。

3.5AutoresizingMask

AutoresizingMask 仅适用于约束父子控件之间的关系,我们知道UIView有个autoresizingMask属性,当UIView的autoresizesSubviews是YES时, 那么一旦bounds发生了变化,当前view的子view会根据它自身的autoresizingMask属性来自动适应其与superView之间的位置和大小。

需要注意的是Autoresizing只能设置父子视图之间的关系,不能设置兄弟视图之间的关系,当然也不能设置完全不相关的两个视图之间的关系。
autoresizing是约束子控件和父控件之间的位置关系的,UIViewController的根view并没有父控件,所以不能通过autoresizing来约束UIViewController的根view。

autoresizingMask是一个枚举类型,默认值为UIViewAutoresizingNone

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

UIViewAutoresizingNone view的frame不会随superview的改变而改变
UIViewAutoresizingFlexibleLeftMargin 自动调整view与superview左边的距离保证右边距离不变
UIViewAutoresizingFlexibleRightMargin 自动调整view与superview右边的距离保证左边距不变
UIViewAutoresizingFlexibleWidth 自动调整view的宽,保证与superView的左右边距不变
UIViewAutoresizingFlexibleHeight 自动调整view的高,保证与superView的顶部和底部距离不变
UIViewAutoresizingFlexibleTopMargin 自动调整view与superview顶部的距离保证底部距离不变
UIViewAutoresizingFlexibleBottomMargin 自动调整view与superview底部部的距离保证顶部距离不变
3.6 AutoLayout
3.6.1 AutoLayout 工作原理

iOS 6 之后推出了AutoLayout,它主要是为了替代Autoresizing,AutoLayout和Autoresizing是不能在同一个项目中共存。
AutoLayout主要由基于Cassary线性方程解析引擎,约束规则组成的,我们在开发的时候会以比较直观的方式对视图控件添加约束,比如某个视图的左边距距离另一个视图的右边边距20pt的距离,每条约束会被转换成一个多元一次方程,一个视图往往有多条约束,这样每个视图都会形成一个多元一次方程组,这些方程组作为布局引擎的输入,经过布局引擎计算后得到视图的frame数据。再进入布局流程,完成整个页面的布局。

下图是整个Auto Layout的流程图:

为一个View添加约束需要如下步骤:

1. 设置View的translatesAutoresizingMaskIntoConstraints属性为NO
2. 根据实际需要的效果创建约束
3. 把约束添加到对应位置,iOS 8+直接通过active激活某条约束;
3.6.2 AutoLayout 约束的组成

下图是一个约束条件组成部分:

  • Item1,Item2:表示该约束关系对应的两个视图,当约束等式表示尺寸时,其中一个Item为nil。
  • Attribute1,Attribute2:NSLayoutAttribute类型,表示约束属性。当约束等式表示尺寸时,其中一个Attribute为NSLayoutAttributeNotAnAttribute,表示占位,无任何意义。

目前可用的约束属性有如下几种类型:

约束属性 意义
NSLayoutAttributeWidth NSLayoutAttributeHeight 视图的尺寸:宽、高
NSLayoutAttributeLeft NSLayoutAttributeRight 视图的X轴方向的位置:左、右
NSLayoutAttributeLeading NSLayoutAttributeTrailing 视图的X轴方向的位置:前、后
NSLayoutAttributeTop NSLayoutAttributeBottom 视图Y轴方向的位置:顶、底
NSLayoutAttributeBaseline 视图Y轴方向的位置:底部基准线
NSLayoutAttributeCenterX NSLayoutAttributeCenterY 视图的中心点:视图在X轴的中心点、视图在Y轴的中心点
  • Relationship:NSLayoutRelation类型,表示约束关系,可以是如下几种关系:
约束关系 意义
NSLayoutRelationLessThanOrEqual 小于等于
NSLayoutRelationGreaterThanOrEqual 大于等于
NSLayoutRelationLessThanOrEqual 等于
  • Multiplier:CGFloat类型,表示倍数关系,一般用于尺寸
  • Constant:CGFloat类型,表示常数。
  • UILayoutPriority: 无论是我们创建的约束,还是系统创建的约束(IntrinsicContentSize相关的约束)都必须指定一个约束优先级
    UILayoutPriority,布局引擎按照线性方程的优先级从高到底对线性方程组进行解析,当设置的约束欠缺会导致线性方程组有多个解,可能导致视图丢失,错位等问题。当设置的约束过多,会导致线程方程组无解。这会产生约束冲突,同样可能造成布局错误,这时候往往有错误Log输出。
    默认创建出来的约束优先级为UILayoutPriorityRequired(1000),其他优先级小于1000的约束称为可选约束。
    Auto Layout Engine会按优先级从高到低满足约束集合中的每一个约束,如果无法满足某个可选约束,则忽略;当优先级不同的两个约束描述的是同一个布局关系,Auto Layout会跳过优先级较低的约束。
3.6.3 AutoLayout 约束的安装与移除
约束安装

在早期的版本中往哪里添加约束需要我们自己判断,这种情况下一般有三种情况:

  1. 约束为对视图本身宽高尺寸进行约束的时候,则约束直接添加到该视图本身
  2. 约束表示两个视图的布局关系的时候,则将约束添加到两个视图所在的视图树的第一个公共祖先
  3. 约束表示两个有层次关系的view之间的约束关系的时候,则将约束添加到层次较高的view上

约束安装需要调用addConstraints方法来进行

约束移除

移除约束需要使用removeConstraint/removeConstraints进行移除,当视图通过removeFromSuperView被整个移除的时候,与该视图相关的全部约束都会自动移除

iOS 版本之后,Auto Layout推出新的接口。NSLayoutConstraint多了一个active属性,用于激活、失效一个约束。不需要再考虑约束安装位置。原本用于添加、移除约束的接口addConstraint/addConstraints、removeConstraint/removeConstraints,接口文档表示在后续的版本升级将会过期,建议避免使用。

UIView *grayView = [[UIView alloc] init];
grayView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:grayView];
grayView.translatesAutoresizingMaskIntoConstraints = NO;

NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100];

[self.view addConstraints:@[left, top]];
[grayView addConstraints:@[width, height]];

// iOS8+
// left.active = YES;
// top.active = YES;
// width.active = YES;
// height.active = YES;

3.6.4 Alignment Rect

在AutoLayout中约束使用的是Alignment Rect 而不是frame,这两者有什么区别呢?一般情况下是没有区别的,只有在一个图片包含阴影、外边框、角标等修饰元素的时候,frame和Alignment Rect才存在差别,结合下面图片可以很明显看出二者的差异。

(a) 是设计给出的切图 ,(c) 是切图的frame,它会将阴影和角标的外边距包括进去,但是这会导致一个问题就是center和right会偏移我们想要的位置,这在居中对齐的时候是很麻烦的一件事情,这种情况下就可以使用Alignment Rect来指定(b)中的框框为自动布局的外框。

如果视图是UIImageView,可以通过UIImage的方法imageWithAlignmentRectInsets来调整对齐矩形,插入内边距。

举个例子,设计给出一个右下角分别有20间距的阴影,导致其中心位置稍稍偏高和偏左,这时候我们要使用imageWithAlignmentRectInsets取出指定矩形区域内的图像。这时候就需要定义一个inset表示距离矩形的顶边、左边、底边和右边的间隙,用来描述从矩形的边移进(使用正值)或移出(使用负值)多远。

UIImage *image = [[UIImage imageNamed:@"Shadowed.png"]  
imageWithAlignmentRectInsets:UIEdgeInsetsMake(0, 0, 20, 20)];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];

对于UIView它提供了方法对应的方法我们在子类中重写它就可以达到效果,由 frame 得到 alignment rect 可以使用:


// The alignment rectangle for the specified frame.
- (CGRect)alignmentRectForFrame:(CGRect)frame;

当然也可以从 alignment rect 反过来得到 frame:

// The frame for the specified alignment rectangle.
- (CGRect)frameForAlignmentRect:(CGRect)alignmentRect;

3.6.5 IntrinsicContentSize

一般情况下,要确定一个View的布局需要确定位置及尺寸,但是对于那些拥有固有内容大小的View,只需要指定它的位置约束就可以了,首先需要明确下IntrinsicContentSize的情况:

  • UIView 没有 IntrinsicContentSize,但是在自定义的View中,可以覆盖intrinsicContentSize方法来返回Intrinsic Content Size,并可以通过调用invalidateIntrinsicContentSize来通知布局系统在下一个布局过程采用新的Intrinsic Content Size。
  • UISlider 在iOS下只定义了width
  • UILabel、UIButton、UISwitch、UITextField的IntrinsicContentSize同时存在width、height
    其中UILabel、UIButton与视图文字数量、字体大小相关,即使没有设置内容,也有IntrinsicContentSize;
  • UITextView、UIImageView的IntrinsicContentSize是动态变化的
    UITextView 的 IntrinsicContentSize 与内容、是否可滚动、约束相关。
    UIImageView 当没有设置image就没有IntrinsicContentSize,当设置了image,IntrinsicContentSize就是设置的image对应的Size;
3.6.6 Compression Resistance && Content Hugging

Compression Resistance 压缩阻力,在有外界力量向内压缩的时候这个值用于表示抗压缩的能力
Content Hugging 内容凝聚力,在有外力将Uiview向外面拉的时候,这个值用于表示拉伸的阻力

对于同一个View,Content Hugging和Compression Resistance不会同时起作用。当一个Label有文字的时候,label会存在一个内容的Size。
如果有外力让其size扩张,Content Hugging会起作用,外力大于Content Hugging的力量,label的size由外力决定,反之,label的Size由内容决定。
如果有外力让其size压缩,Compression Resistance会起作用,外力大于Compression Resistance的力量,label的size由外力决定,反之,label的Size由内容决定。

[label1 setContentCompressionResistancePriority:UILayoutPriorityRequired-1 forAxis:UILayoutConstraintAxisHorizontal];
[label1 setContentHuggingPriority:UILayoutPriorityRequired-1 forAxis:UILayoutConstraintAxisHorizontal];
3.7 NSLayoutAnchor

NSLayoutAnchor 是iOS 9之后推出的一种比NSLayoutConstraint更方便的方案,它将NSLayoutAnchor作为UIView的属性:

布局锚点类型 对应的子类 布局属性
X轴方向 NSLayoutXAxisAnchor leadingAnchor、trailingAnchor、leftAnchor、rightAnchor、centerXAnchor
Y轴方向 NSLayoutYAxisAnchor topAnchor、bottomAnchor、centerYAnchor、firstBaselineAnchor、lastBaselineAnchor
尺寸 NSLayoutDimension widthAnchor、heightAnchor

这里有一篇关于NSLayoutAnchor写得比较好的文章可以供大家深入学习。

3.8 其他比较少用的布局方式
3.8.1 VFL

使用字符串来描述约束关系,这种没有校验的方式,一般项目都不会采用。

3.8.2 sizeClass

sizeClass 只能用于StoryBoard,个人比较偏向在项目中使用纯代码实现布局,StoryBoard编译速度慢,并且团队合作的情况下如果出现冲突很难解决。

3.8.3 Interface Builder

同上原因:不喜欢用storyboard,喜欢纯代码方式实现布局。

3.8.4 UIStackView

UIStackView 是iOS 9 推出的布局方案,如果需要兼容之前的版本可以使用FDStackView这个开源库,UIStackView在某些成行成列的布局中比较好用,比如九宫格布局之类的,如果是比较复杂的布局,就会有比较深的嵌套,维护起来很蛋疼,建议谨慎使用。

3.8 三方开源布局引擎对比
3.8.1 Masonry 17438 Stars

Masonry是我接触iOS开发时候使用的第一个布局引擎,它也是目前遇到的Stars数量最多的一个布局引擎,如果没有Masonry我会觉得AutoLayout是一个蛋疼的设计,有了Masonry有种从地狱到天堂的感觉,但是它底层是基于AutoLayout,所以在复杂界面性能上较差,因此在项目中期切换到基于frame封装的布局引擎了。但是整个封装还是十分优雅的,在中小型项目或者简单布局完全可以胜任。

3.8.2 YogaKit 12141 Stars

这是我个人项目中用的一个布局引擎,它是一个跨平台的底层基于C++实现的,使用的是目前前端比较火的Flex Box,之所以用它有如下理由:

  1. 它是跨平台的Android,iOS,Reactive Native都可以使用,在iOS平台上对应的库为YogaKit,在Android 平台上对应的库为Litho
  2. 基于FlexBox的这个是前端必须掌握的布局模式,使用它可以缩小下技术栈。
  3. 轻量,易用,基于frame布局性能上面有保证

其实最重要的原因还是想统一下技术栈。

3.8.3 ComponentKit 5053 Stars

ComponentKit 是Facebook推出的,它最初是为Facebook News Feed开发的,目前它被应用于Facebook的全部iOS应用。它可以在后台对UI提前测量和布局,因此不会阻碍UI线程。在 ComponentKit中我们处理的对象是一个个Component,它是一个NSObject子类,而不是UIView,我们在描述布局的时候只需要描述component,然后提交给ComponentKit渲染,如果UI有变化,只需要重新生成component然后全部刷新,ComponentKit负责处理这些变化。总结来说ComponentKit有如下优点:

  1. 将对UI的变化转换成状态的变化
  2. 可以在后台进行测量和布局
  3. 可以对布局进行缓存

它使用起来有点像Flutter的UI布局,对于描述复杂界面,整个代码会显得很庞大,不过结合懒加载写法这一点可以克服,后续有机会会在自己的业余项目中体验下它,这里就不再做介绍了。

3.8.4 LayoutKit 2872 Stars

说实话在选型的时候LayoutKit有吸引到我,如果不是因为Yoga可以跨平台我可能会选择它,下面是LayoutKit文档中对它的描述:

LayoutKit has many benefits over using Auto Layout:

  • Fast: LayoutKit is as fast as manual layout code and is significantly faster than Auto Layout.
  • Asynchronous: Layouts can be computed in a background thread so user interactions are not interrupted.
  • Declarative: Layouts are declared with immutable data structures. This makes layout code easier to develop, document, code review, test, debug, profile, and maintain.
  • Cacheable: Layout results are immutable data structures so they can be precomputed in the background and cached to increase user perceived performance.

LayoutKit also provides benefits that make it as easy to use as Auto Layout:

UIKit friendly: LayoutKit produces UIViews and also provides an adapter that makes it easy to use with UITableView and UICollectionView.
Internationalization: LayoutKit automatically adjusts view frames for right-to-left languages.
Swift: LayoutKit can be used in Swift applications and playgrounds.
Tested and production ready: LayoutKit is covered by unit tests and is being used inside of recent versions of the LinkedIn and LinkedIn Job Search iOS apps.
Open-source: Not a black box like Auto Layout.
Apache License (v2): Your lawyers will be happy that there are no patent shenanigans.

总结起来就是: 速度块, 异步,对结果进行缓存,可以很方便得支持从右往左的语言,可以在Swift语言中使用。

3.8.5 MyLinearLayout 3503 Stars

MyLinearLayout是国内开发者开发的它是基于frame布局的方式,TangramKit是它的Swift版本,MyLayout功能强大而且简单易用,不论在功能,用法还是性能上都是一个不错的布局引擎。也是个十分值得推荐的布局引擎。

3.8.6 Texture 5737 Stars

Texture 内部支持ASLayoutSpect 和 Yoga 两种布局引擎,但是因为如果整个引入会显得太过庞大,目前还没考虑切换到Texture 不过后续会在列表页面使用它Texture + IGListKit应该是一个不错的组合。

Contents
  1. 1. 0. 较好的相关网站
  2. 2. 1. 目录
  3. 3. 2. 开篇叨叨
  4. 4. 3. 正文
    1. 4.1. 3.1 iOS界面开发中常用的概念
    2. 4.2. 3.2 点(Point) 屏幕像素 (Pixel) 对角线长度 ,屏幕密度,屏幕模式,宽高比
    3. 4.3. 3.3 布局坐标系及关键的属性
    4. 4.4. 3.4 iOS布局相关函数
    5. 4.5. 3.4.1 frame布局
    6. 4.6. 3.4.1.1 自动触发布局
    7. 4.7. 3.4.1.2 手动触发布局
    8. 4.8. 3.4.1.3 布局回调节点
    9. 4.9. 3.4.2 自动布局
    10. 4.10. 3.4.3 显示
    11. 4.11. 3.5AutoresizingMask
    12. 4.12. 3.6 AutoLayout
    13. 4.13. 3.6.1 AutoLayout 工作原理
    14. 4.14. 3.6.2 AutoLayout 约束的组成
    15. 4.15. 3.6.3 AutoLayout 约束的安装与移除
    16. 4.16. 约束安装
    17. 4.17. 约束移除
    18. 4.18. 3.6.4 Alignment Rect
    19. 4.19. 3.6.5 IntrinsicContentSize
    20. 4.20. 3.6.6 Compression Resistance && Content Hugging
    21. 4.21. 3.7 NSLayoutAnchor
    22. 4.22. 3.8 其他比较少用的布局方式
    23. 4.23. 3.8.1 VFL
    24. 4.24. 3.8.2 sizeClass
    25. 4.25. 3.8.3 Interface Builder
    26. 4.26. 3.8.4 UIStackView
    27. 4.27. 3.8 三方开源布局引擎对比
    28. 4.28. 3.8.1 Masonry 17438 Stars
    29. 4.29. 3.8.2 YogaKit 12141 Stars
    30. 4.30. 3.8.3 ComponentKit 5053 Stars
    31. 4.31. 3.8.4 LayoutKit 2872 Stars
    32. 4.32. 3.8.5 MyLinearLayout 3503 Stars
    33. 4.33. 3.8.6 Texture 5737 Stars