史上比较用心的纯代码实现 AutoLayout
入职有两三个月了吧,都是使用 Objective-C 纯代码(虽然有时候偷偷参杂一些 Swift 开源库)来编写公司APP,写布局的时候几乎都是要么在初始化的时候用 initWithFrame,要么就初始化完毕之后用 view.frame。虽然这种方法很直观,一眼就可以看出这个 view 的位置以及大小,但是坏处也是有的,比如说在计算的时候麻烦等等。
概述
使用 Objective-C 纯代码编写 AutoLayout,看 AutoLayout 的字面理解就是自动布局,听起来好像蛮屌的样子。说白了就是适配:适应、兼容各种不同的情况,包括不同版本的操作系统的适配(系统适配)和不同屏幕尺寸的适配(屏幕适配)。
在 Storyboard 中,AutoLayout 有以下 3 个常用面板:
Align(对齐)
Pin(相对)
Resolve Auto Layout Issues(约束处理)
在 Storyboard 中实现 AutoLayout 我就不在本文讲解,因为讲了就是违背了不忘初心,方得始终的标题了。
Talk is cheap, show me the code
先说一下用代码实现 AutoLayout 步骤,别眨眼:
利用 NSLayoutConstraint 类创建具体的约束对象;
添加约束对象到相应的 view 上,代码有这两种:
1
2
|
- (void)addConstraint:(NSLayoutConstraint *)constraint; - (void)addConstraints:(NSArray *)constraints; |
或许有人问了,原来才两个步骤就可以了,我刚刚裤子都脱了,你就给我看这个?!
话不多说,马上 show you the code !
先看看我们使用 frame 的方式是如何确定一个 view 的位置的:
1
2
3
4
5
6
7
|
- (void)viewDidLoad { [ super viewDidLoad]; self.title = @ "使用 frame 的方式" ; UIView *purpleView = [[UIView alloc] initWithFrame:CGRectMake(100, 200, 150, 150)]; purpleView.backgroundColor = [UIColor purpleColor]; [self.view addSubview:purpleView]; } |
代码很简单,运行效果如下:
运行效果
再来看看 AutoLayout 的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
- (void)viewDidLoad { [ super viewDidLoad]; self.title = @ "使用 AutoLayout 的方式" ; UIView *purpleView = [[UIView alloc] init]; purpleView.backgroundColor = [UIColor purpleColor]; // 禁止将 AutoresizingMask 转换为 Constraints purpleView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:purpleView]; // 添加 width 约束 NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:150]; [purpleView addConstraint:widthConstraint]; // 添加 height 约束 NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:150]; [purpleView addConstraint:heightConstraint]; // 添加 left 约束 NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:100]; [self.view addConstraint:leftConstraint]; // 添加 top 约束 NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:purpleView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:200]; [self.view addConstraint:topConstraint]; } |
看完这段代码,我收到了惊吓!我被这一大段代码吓到了,很多童鞋看到那么简单的布局需要写那么多代码,可能就被吓跑了。我只能说一句:先不要走,待我慢慢解释~
创建约束对象(NSLayoutConstraint)的常用方法
一个 NSLayoutConstraint 对象就代表一个约束。
1
|
+ (id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c; |
总共有 7 个参数,那就以 leftConstraint 为例吧介绍这 7 个参数吧
view1: 要约束的控件(purpleView)
attr1: 约束的类型(常量),就是要做怎么样的约束,大家可以进去看看都有什么常量(这里是NSLayoutAttributeLeft)
relation: 与参照控件之间的关系(常量),包括等于、大于等于、小于等于(NSLayoutRelationEqual 是指等于)
view2: 参照的控件(self.view)
attr2: 约束的类型(常量),就是要做怎么样的约束,大家可以进去看看都有什么常量(这里是NSLayoutAttributeLeft)(NSLayoutAttributeLeft)
multiplier: 乘数,就是多少倍(1.0)
c: 常量,做好了上述的约束之后会加上这个常量(100)
所以 leftConstraint 就是代表:要约束的控件purpleView 的左间距是等于参照控件 self.view 的左间距的 1.0 倍加上 100。
所以我们得出 AutoLayout 的核心计算公式:
1
|
obj1.property1 =(obj2.property2 * multiplier)+ constant value |
添加约束(addConstraint)的规则
在创建约束了之后,需要将其添加到作用的控件上才能生效,注意在添加约束的时候目标控件需要遵循以下规则(这里控件就用 view 简单表示吧):
(1)对于两个同层级 view 之间的约束关系,添加到它们的父 view 上
(2)对于两个不同层级 view 之间的约束关系,添加到他们最近的共同父 view 上
(3)对于有层次关系的两个 view 之间的约束关系,添加到层次较高的父 view 上
(4)对于比如长宽之类的,只作用在该 view 自己身上的话,添加到该 view 自己上,不用图了吧。
可以看出,widthConstraint 和 Constraint 属于第(4)种,leftConstraint 和 rightConstraint 属于第(3)种。
代码实现 AutoLayout 的注意事项
如果只是创建和添加了约束,是不能正常运行的,要做好以下的工作:
(1)要先禁止 autoresizing 功能,防止 AutoresizingMask 转换成 Constraints,避免造成冲突,需要设置 view 的下面属性为 NO:
1
|
view.translatesAutoresizingMaskIntoConstraints = NO; |
(2) 添加约束之前,一定要保证相关控件都已经在各自的父控件上。用上面的例子就是 [self.view addSubview:purpleView]; 一定要放在添加 left 约束之前,否则程序会 crash,因为要确保 purpleView 要已经在 self.view 上了。建议先写 [self.view addSubview:purpleView]; 之后,再专心写约束。
(3)不用再给 view 设置 frame
看到了吧,那么简单的一个界面,用 AutoLayout 实现的话竟然要那么多代码,感觉上并没有那么方便是吧?
其实 AutoLayout 要看应用内容决定,上面只是一个使用的 demo。如果你的内容是信息众多,同时需要展示的类别也很多,尺寸动态不定,比如说微博列表、QQ 动态列表等等,写这些复杂界面使用 AutoLayout 能给予(jǐ yǔ??)很大的帮助。
Apple 为了简化 AutoLayout 复杂的代码,开发了一种 VFL 语言(Visual format language),事实上没看见简化多少,而且还有比较大的局限性,这里就不介绍了,想了解的童鞋自己 Google 去。
算了,给个官方链接吧:Visual Format Language。
如何优雅的代码编写 AutoLayout
看到了 Apple 自带的 AutoLayout 实现方式,感觉实在是太恶心了,那么如何优雅的代码编写 AutoLayout 呢?
—— 使用第三方框架 Masonry。GitHub:https://github.com/SnapKit/Masonry,看它的介绍,感觉挺牛掰的:
Harness
the power of AutoLayout NSLayoutConstraints with a simplified,
chainable and expressive syntax. Supports iOS and OSX Auto Layout.
看完 README.md 文件发现的确蛮优雅的。
先一览 Masonry 是如何实现 AutoLayout 的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#import "ViewController.h" #import "Masonry.h" // 第三方或自己写的用引号,系统自带用双引号。 @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [ super viewDidLoad]; UIView *purpleView = [[UIView alloc] init]; purpleView.backgroundColor = [UIColor purpleColor]; [self.view addSubview:purpleView]; [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { // 在这个 block 里面,利用 make 对象创建约束 make.size.mas_equalTo(CGSizeMake(100, 100)); make.center.mas_equalTo(self.view); }]; } |
运行效果:
创建一个长和宽均为 100、与父 view 居中的 view
注意:purpleView.translatesAutoresizingMaskIntoConstraints = NO;不需要在这里写了,因为 Masonry 已经写好了。
Masonry 开车,赶紧上车
一步一步跟着来,哈哈嘻嘻
1
2
3
4
5
6
7
|
// 长宽均为 100,粘着父 view 右下角 [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(@100); make.height.equalTo(@100); make.right.equalTo(self.view); make.bottom.equalTo(self.view); }]; |
1
2
3
4
5
6
7
8
9
10
|
// 长宽均为 100,粘着父 view 右下角,间距为 16 [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(@100); make.height.equalTo(@100); // 这里也可以写 make.right.equalTo(self.view.mas_right).offset(-16); // 为了增强可读性,可以在 .offset 前加上 .with 或者 .and: make.right.equalTo(self.view).with.offset(-16); 看自己习惯吧 make.right.equalTo(self.view).offset(-16); // 这里也可以写 make.right.equalTo(self.view.mas_bottom).offset(-16); make.bottom.equalTo(self.view).offset(-16); }]; |
看到上面代码的包装好的 @100,其实也可以直接传值 100,不过要把 equalTo 改成 mas_equalTo,这样它就自动帮你包装好了。
1
2
|
make.width.mas_equalTo(100); make.height.mas_equalTo(100); |
其实 mas_equalTo 就是一个宏,大家可以进去看看定义。
mas_equalTo 这个方法会对参数进行包装
equalTo 这个方法不会对参数进行包装
mas_equalTo 的功能强于 equalTo
大家可能会觉得有点儿晕,有时候用 mas_equalTo,有时候用 equalTo,其实大家可以在 pch 文件里定义两个宏,就可以完美解决这个纠结问题。注意要写在 #import "Masonry.h" 前面。
1
2
3
4
|
//define this constant if you want to use Masonry without the 'mas_' prefix,这样子 `mas_width` 等就可以写成 `width` #define MAS_SHORTHAND //define this constant if you want to enable auto-boxing for default syntax,这样子 `mas_equalTo` 和 `equalTo` 就没有区别了 #define MAS_SHORTHAND_GLOBALS |
好,现在来一个稍微比刚才的复杂一点点的界面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
- (void)viewDidLoad { [ super viewDidLoad]; UIView *purpleView = [[UIView alloc] init]; purpleView.backgroundColor = [UIColor purpleColor]; [self.view addSubview:purpleView]; UIView *orangeView = [[UIView alloc] init]; orangeView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:orangeView]; CGFloat margin = 16; CGFloat height = 32; [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).offset(margin); make.bottom.equalTo(self.view).offset(-margin); make.right.equalTo(orangeView.left).offset(-margin); make.height.equalTo(height); make.width.equalTo(orangeView); }]; [orangeView mas_makeConstraints:^(MASConstraintMaker *make) { make.bottom.equalTo(self.view).offset(-margin); make.right.equalTo(self.view).offset(-margin); make.height.equalTo(height); }]; } |
两个等高等宽的 view 平分屏幕宽度,带有间隙
其实实现这个界面有很多中写法,大家可以试试,比如说这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
- (void)viewDidLoad { ... [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).offset(margin); make.bottom.equalTo(self.view).offset(-margin); make.right.equalTo(orangeView.left).offset(-margin); make.height.equalTo(height); make.height.equalTo(orangeView); make.width.equalTo(orangeView); make.top.equalTo(orangeView); }]; [orangeView mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(self.view).offset(-margin); }]; } |
总结
其实 Masonry 的文档已经很详细了,建议大家去看文档,我写这个主要是为了做这个界面的 Tableview 上下拉阻尼效果而准备的
对我粗暴~
本文作者:伯乐在线 - 小良 。
史上比较用心的纯代码实现 AutoLayout的更多相关文章
- 史上最用心的 iOS App 上架流程
题记 麻痹起来嗨!看网上那么多的教程,依然在我心爱的爱屁屁在上架的时候遇到各种 J8 问题,最大的问题就是:Xcode 证书什么的,Provisioning Profile 什么的,Debug 什么的 ...
- 史上最用心的iOS App上架流程【转】
转:http://www.jianshu.com/p/16fa56eacb5e 题记 麻痹起来嗨!看网上那么多的教程,依然在我心爱的爱屁屁在上架的时候遇到各种 J8 问题,最大的问题就是:Xcode ...
- CodeMixerPro工具,完美替代ChaosTool,iOS添加垃圾代码工具,代码混淆工具,代码生成器,史上最好用的垃圾代码添加工具,自己开发的小工具
新工具 ProjectTool 已上线 这是一款快速写白包工具,秒级别写H5游戏壳包,可视化操作,极易使用,支持Swift.Objecive-C双语言 扣扣交流群:811715780 进入 Proje ...
- ChaosTool,iOS添加垃圾代码工具,代码混淆工具,代码生成器,史上最好用的垃圾代码添加工具,自己开发的小工具
最近在H5游戏项目中需要添加垃圾代码作混淆,提高过审机率.手动添加太费时费力,在网上并没有找到合适的比较好的工具,就自己动手写了一个垃圾代码添加工具,命名为ChaosTool. 扣扣交流群:81171 ...
- CodeMixer工具,完美替代ChaosTool,iOS添加垃圾代码工具,代码混淆工具,代码生成器,史上最好用的垃圾代码添加工具,自己开发的小工具
新工具 ProjectTool 已上线 这是一款快速写白包工具,秒级别写H5游戏壳包,可视化操作,极易使用,支持Swift.Objecive-C双语言 扣扣交流群:811715780 进入 Proje ...
- 史上最坑 idea 更改代码不生效
原来, 如果本地多次调整过系统时间,那么gradle 的缓存 会缓存 你的 上次编译时间再未来,那么你再怎么编译,都很难生效,即使删除了生成的字节码目录. 然后invalidate caches/re ...
- Swift语言之View,Button控件实现小方块在界面上的移动(纯代码实现)
import UIKit class ViewController: UIViewController { var diamonds:UIView! var diamondsXY = CGRectMa ...
- PureLayout,使用纯代码写AutoLayout
为iOS和OS X的自动布局最终的API -- 令人印象深刻的简单,非常强大. PureLayout延伸的UIView /NSView , NSArray,和NSLayoutConstraint与之后 ...
- python 穷举法 算24点(史上最简短代码)
本来想用回溯法实现 算24点.题目都拟好了,就是<python 回溯法 子集树模板 系列 -- 7.24点>.无奈想了一天,没有头绪.只好改用暴力穷举法. 思路说明 根据四个数,三个运算符 ...
随机推荐
- JavaScript 语言基础知识点总结
网上找到的一份JavaScript 语言基础知识点总结,还不错,挺全面的. (来自:http://t.cn/zjbXMmi @刘巍峰 分享 )
- linux使用su切换用户提示 Authentication failure的解决方法& 复制文件时,报cp: omitting directory `XXX'
linux使用su切换用户提示 Authentication failure的解决方法:这个问题产生的原因是由于ubtun系统默认是没有激活root用户的,需要我们手工进行操作,在命令行界面下,或者在 ...
- 任正非:华为三十年大限快到了 想不死就得新生(建立战略预备队)cool
华为心声社区官方微信今日发布了任正非8月15日在华为公司内部做的关于战略预备队建设汇报的讲话.讲话内容中提到,华为公司需要组织.结构.人才等所有一切都变化,通过变化使新的东西成长起来. 任正非表示 ...
- Windows.document对象
一.找到元素: docunment.getElementById("id"):根据id找,最多找一个:var a =docunment.getElementById("i ...
- Android Animation初识
3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中又引入了一个新的动画系统:property animation,这三 ...
- VirtualBox虚拟机中启用usb3.0却无法显示u盘的解决方法
主机系统为win7 64位,由于工作需要,安装了Virtualbox 5.18虚拟机,virtaulbox中安装了win7 32系统.以为下启用usb 3.0的步骤: 1.宿主机要支持 usb 3.0 ...
- 2.5.6 使用progressDialog创建进度对话框
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout ...
- Android Intent.FLAG_NEW_TASK详解,包括其他的标记的一些解释
本文大部分参考自 http://blog.csdn.net/mayingcai1987/article/details/6200909 ,对原文中的讲解FLAG_NEW_TASK地方加了一些自己的观点 ...
- Timer计时不准确的解决方案 每次都重新调整,修正误差
http://stackoverflow.com/questions/29722838/system-timers-timer-steadily-increasing-the-interval 需要在 ...
- fuser可以用于系统安全检查。
fuser可以用于系统安全检查.用fuser查看哪些用户和进程在某些地方作什么:fuser -cu /root 简略显示fuser -muv /mnt3 分列显示