史上比较用心的纯代码实现AutoLayout

入职有两三个月了吧,都是使用 Objective-C 纯代码(虽然有时候偷偷参杂一些 Swift 开源库)来编写公司APP,写布局的时候几乎都是要么在初始化的时候用 initWithFrame,要么就初始化完毕之后用 view.frame。虽然这种方法很直观,一眼就可以看出这个 view 的位置以及大小,但是坏处也是有的,比如说在计算的时候麻烦等等。

概述

使用 Objective-C 纯代码编写 AutoLayout,看 AutoLayout 的字面理解就是自动布局,听起来好像蛮屌的样子。说白了就是适配:适应、兼容各种不同的情况,包括不同版本的操作系统的适配(系统适配)和不同屏幕尺寸的适配(屏幕适配)。

在 Storyboard 中,AutoLayout 有以下 3 个常用面板:

1.Align(对齐)

2.Pin(相对)

3.Resolve Auto Layout Issues(约束处理)

在 Storyboard 中实现 AutoLayout 我就不在本文讲解,因为讲了就是违背了不忘初心,方得始终的标题了。

Talk is cheap, show me the code

先说一下用代码实现 AutoLayout 步骤,别眨眼:

  1. 利用 NSLayoutConstraint 类创建具体的约束对象;
  2. 添加约束对象到相应的 view 上,代码有这两种:

  1. - (void)addConstraint:(NSLayoutConstraint *)constraint; 
  2.  
  3. - (void)addConstraints:(NSArray *)constraints;  

或许有人问了,原来才两个步骤就可以了,我刚刚裤子都脱了,你就给我看这个?!

话不多说,马上 show you the code !

先看看我们使用 frame 的方式是如何确定一个 view 的位置的:


  1. - (void)viewDidLoad { 
  2.  
  3.     [super viewDidLoad]; 
  4.  
  5.     self.title = @"使用 frame 的方式"; 
  6.  
  7.     UIView *purpleView = [[UIView alloc] initWithFrame:CGRectMake(100, 200, 150, 150)]; 
  8.  
  9.     purpleView.backgroundColor = [UIColor purpleColor]; 
  10.  
  11.     [self.view addSubview:purpleView]; 
  12.  
  13. }  

代码很简单,运行效果如下:

再来看看 AutoLayout 的实现:


  1. - (void)viewDidLoad { 
  2.  
  3.     [super viewDidLoad]; 
  4.  
  5.     self.title = @"使用 AutoLayout 的方式"; 
  6.  
  7.     UIView *purpleView = [[UIView alloc] init]; 
  8.  
  9.     purpleView.backgroundColor = [UIColor purpleColor]; 
  10.  
  11.     // 禁止将 AutoresizingMask 转换为 Constraints 
  12.  
  13.     purpleView.translatesAutoresizingMaskIntoConstraints = NO; 
  14.  
  15.     [self.view addSubview:purpleView]; 
  16.  
  17.   
  18.  
  19.     // 添加 width 约束 
  20.  
  21.     NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:purpleViewattribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttributemultiplier:0.0 constant:150]; 
  22.  
  23.     [purpleView addConstraint:widthConstraint]; 
  24.  
  25.   
  26.  
  27.     // 添加 height 约束 
  28.  
  29.     NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:purpleViewattribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttributemultiplier:0.0 constant:150]; 
  30.  
  31.     [purpleView addConstraint:heightConstraint]; 
  32.  
  33.   
  34.  
  35.     // 添加 left 约束 
  36.  
  37.     NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:purpleViewattribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeftmultiplier:1.0 constant:100]; 
  38.  
  39.     [self.view addConstraint:leftConstraint]; 
  40.  
  41.   
  42.  
  43.     // 添加 top 约束 
  44.  
  45.     NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:purpleViewattribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTopmultiplier:1.0 constant:200]; 
  46.  
  47.     [self.view addConstraint:topConstraint]; 
  48.  

看完这段代码,我收到了惊吓!我被这一大段代码吓到了,很多童鞋看到那么简单的布局需要写那么多代码,可能就被吓跑了。我只能说一句:先不要走,待我慢慢解释~

创建约束对象(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 

1.添加约束(addConstraint)的规则

在创建约束了之后,需要将其添加到作用的控件上才能生效,注意在添加约束的时候目标控件需要遵循以下规则(这里控件就用 view 简单表示吧):

(1)对于两个同层级 view 之间的约束关系,添加到它们的父 view 上

对于两个同层级 view 之间的约束关系,添加到它们的父 view 上

(2)对于两个不同层级 view 之间的约束关系,添加到他们最近的共同父 view 上

对于两个不同层级 view 之间的约束关系,添加到他们最近的共同父 view 上

(3)对于有层次关系的两个 view 之间的约束关系,添加到层次较高的父 view 上

对于有层次关系的两个 view 之间的约束关系,添加到层次较高的父 view 上

(4)对于比如长宽之类的,只作用在该 view 自己身上的话,添加到该 view 自己上,不用图了吧

可以看出,widthConstraint 和 Constraint 属于第(4)种,leftConstraint 和 rightConstraint 属于第(3)种。

1.代码实现 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 去。

算了,给个官方链接吧:

https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html

如何优雅的代码编写 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. #import "ViewController.h" 
  2.  
  3. #import "Masonry.h" // 第三方或自己写的用引号,系统自带用双引号。 
  4.  
  5.   
  6.  
  7. @interface ViewController () 
  8.  
  9. @end 
  10.  
  11.   
  12.  
  13. @implementation ViewController 
  14.  
  15.   
  16.  
  17. - (void)viewDidLoad { 
  18.  
  19.     [super viewDidLoad]; 
  20.  
  21.   
  22.  
  23.     UIView *purpleView = [[UIView alloc] init]; 
  24.  
  25.     purpleView.backgroundColor = [UIColor purpleColor]; 
  26.  
  27.     [self.view addSubview:purpleView]; 
  28.  
  29.   
  30.  
  31.     [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { 
  32.  
  33.         // 在这个 block 里面,利用 make 对象创建约束 
  34.  
  35.         make.size.mas_equalTo(CGSizeMake(100, 100)); 
  36.  
  37.         make.center.mas_equalTo(self.view); 
  38.  
  39.     }]; 
  40.  
  41. }  

运行效果:

创建一个长和宽均为 100、与父 view 居中的 view

注意:purpleView.translatesAutoresizingMaskIntoConstraints = NO;不需要在这里写了,因为 Masonry 已经写好了。

Masonry 开车,赶紧上车

一步一步跟着来,哈哈嘻嘻


  1. // 长宽均为 100,粘着父 view 右下角 
  2.  
  3.    [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { 
  4.  
  5.        make.width.equalTo(@100); 
  6.  
  7.        make.height.equalTo(@100); 
  8.  
  9.        make.right.equalTo(self.view); 
  10.  
  11.        make.bottom.equalTo(self.view); 
  12.  
  13.    }];   

长宽均为 100,粘着父 view 右下角


  1. // 长宽均为 100,粘着父 view 右下角,间距为 16 
  2.  
  3.    [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { 
  4.  
  5.        make.width.equalTo(@100); 
  6.  
  7.        make.height.equalTo(@100); 
  8.  
  9.        // 这里也可以写 make.right.equalTo(self.view.mas_right).offset(-16); 
  10.  
  11.        // 为了增强可读性,可以在 .offset 前加上 .with 或者 .and: make.right.equalTo(self.view).with.offset(-16); 看自己习惯吧 
  12.  
  13.        make.right.equalTo(self.view).offset(-16); 
  14.  
  15.        // 这里也可以写 make.right.equalTo(self.view.mas_bottom).offset(-16); 
  16.  
  17.        make.bottom.equalTo(self.view).offset(-16); 
  18.  
  19.    }];   

长宽均为 100,粘着父 view 右下角,间距为 16

看到上面代码的包装好的 @100,其实也可以直接传值 100,不过要把 equalTo 改成 mas_equalTo,这样它就自动帮你包装好了。


  1. make.width.mas_equalTo(100); 
  2.  
  3. make.height.mas_equalTo(100);  

其实 mas_equalTo 就是一个宏,大家可以进去看看定义。

  • mas_equalTo 这个方法会对参数进行包装
  • equalTo 这个方法不会对参数进行包装
  • mas_equalTo 的功能强于 equalTo

大家可能会觉得有点儿晕,有时候用 mas_equalTo,有时候用 equalTo,其实大家可以在 pch 文件里定义两个宏,就可以完美解决这个纠结问题。注意要写在 #import "Masonry.h" 前面。


  1. //define this constant if you want to use Masonry without the 'mas_' prefix,这样子 `mas_width` 等就可以写成 `width` 
  2.  
  3. #define MAS_SHORTHAND 
  4.  
  5.   
  6.  
  7. //define this constant if you want to enable auto-boxing for default syntax,这样子 `mas_equalTo` 和 `equalTo` 就没有区别了 
  8.  
  9. #define MAS_SHORTHAND_GLOBALS  

好,现在来一个稍微比刚才的复杂一点点的界面:


  1. - (void)viewDidLoad { 
  2.  
  3.     [super viewDidLoad]; 
  4.  
  5.   
  6.  
  7.     UIView *purpleView = [[UIView alloc] init]; 
  8.  
  9.     purpleView.backgroundColor = [UIColor purpleColor]; 
  10.  
  11.     [self.view addSubview:purpleView]; 
  12.  
  13.   
  14.  
  15.     UIView *orangeView = [[UIView alloc] init]; 
  16.  
  17.     orangeView.backgroundColor = [UIColor orangeColor]; 
  18.  
  19.     [self.view addSubview:orangeView]; 
  20.  
  21.   
  22.  
  23.     CGFloat margin = 16; 
  24.  
  25.     CGFloat height = 32; 
  26.  
  27.     [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { 
  28.  
  29.         make.left.equalTo(self.view).offset(margin); 
  30.  
  31.         make.bottom.equalTo(self.view).offset(-margin); 
  32.  
  33.         make.right.equalTo(orangeView.left).offset(-margin); 
  34.  
  35.         make.height.equalTo(height); 
  36.  
  37.         make.width.equalTo(orangeView); 
  38.  
  39.     }]; 
  40.  
  41.   
  42.  
  43.     [orangeView mas_makeConstraints:^(MASConstraintMaker *make) { 
  44.  
  45.         make.bottom.equalTo(self.view).offset(-margin); 
  46.  
  47.         make.right.equalTo(self.view).offset(-margin); 
  48.  
  49.         make.height.equalTo(height); 
  50.  
  51.     }]; 
  52.  
  53. }  

两个等高等宽的 view 平分屏幕宽度,带有间隙

其实实现这个界面有很多中写法,大家可以试试,比如说这样写:


  1. - (void)viewDidLoad {   
  2.  
  3.   
  4.  
  5.     ... 
  6.  
  7.   
  8.  
  9.     [purpleView mas_makeConstraints:^(MASConstraintMaker *make) { 
  10.  
  11.         make.left.equalTo(self.view).offset(margin); 
  12.  
  13.         make.bottom.equalTo(self.view).offset(-margin); 
  14.  
  15.         make.right.equalTo(orangeView.left).offset(-margin); 
  16.  
  17.         make.height.equalTo(height); 
  18.  
  19.         make.height.equalTo(orangeView); 
  20.  
  21.         make.width.equalTo(orangeView); 
  22.  
  23.         make.top.equalTo(orangeView); 
  24.  
  25.     }]; 
  26.  
  27.   
  28.  
  29.     [orangeView mas_makeConstraints:^(MASConstraintMaker *make) { 
  30.  
  31.         make.right.equalTo(self.view).offset(-margin); 
  32.  
  33.     }]; 
  34.  
  35. }  

总结

其实 Masonry 的文档已经很详细了,建议大家去看文档,我写这个主要是为了做这个界面的 Tableview 上下拉阻尼效果而准备的

作者:伯乐专栏/小良

来源:51CTO

时间: 2024-10-09 16:58:07

史上比较用心的纯代码实现AutoLayout的相关文章

iOS界面布局之三——纯代码的autoLayout及布局动画

iOS界面布局之三--纯代码的autoLayout及布局动画 一.引言         关于界面布局,apple的策略已经趋于成熟,autolayout的优势在开发中也已经展现的淋漓尽致.除了使用storyBoard进行布局约束的拖拽,有时我们也需要在代码中进行autolayout的布局设置,Masonry库可以方便的创建约束属性,实际上,我们也没有必要再使用系统原生的代码来创建和设置约束,这篇博客只作为使用的方法备忘.前几篇布局介绍的链接如下: 使用autoresizing进行界面布局:htt

python 穷举法 算24点(史上最简短代码)

本来想用回溯法实现 算24点.题目都拟好了,就是<python 回溯法 子集树模板 系列 -- 7.24点>.无奈想了一天,没有头绪.只好改用暴力穷举法. 思路说明 根据四个数,三个运算符,构造三种中缀表达式,遍历,计算每一种可能 显然可能的形式不止三种.但是,其它的形式要么得不到24点,要么在加.乘意义下可以转化为这三种形式的表达式! 使用内置的eval函数计算中缀表达式,使得代码变得非常简洁! 完整代码 # 作者:hhh5460 # 时间:2017年6月3日 import itertool

追问史上最惨烈电商价格战:传统大鳄PK纯电商

传统大鳄PK纯电商:拼的是供应链 成本节约 [圆桌嘉宾] 国美网上商城总经理韩德鹏 苏宁易购执行副总裁李斌 天猫电器城总经理谭飙 新七天电器网C EO左英杰 京东商城负责人 一石激起千层浪,国美与苏宁在电商领域大展拳脚,触发京东.天猫等电商巨头发起史上最激烈的价格战. 天猫宣布投入2亿元启动持续整个夏季的促销活动,京东拿出5亿元对家电产品进行促销,苏宁易购投入10亿货源降价30%后,最新加入战局的易迅网则将联合腾讯投入5亿元的让利额度及营销资源到3C数码与电器商品上,各家投入金额合计已超过20亿

史上最惨烈电商价格战:传统大鳄PK纯电商

(来源:南方都市报南都网) [圆桌嘉宾] 国美(微博)网上商城总经理韩德鹏 苏宁易购执行副总裁李斌 天猫电器城总经理谭飙 新七天电器网CEO左英杰 京东商城负责人 一石激起千层浪,国美与苏宁在电商领域大展拳脚,触发京东.天猫等电商巨头发起史上最激烈的价格战. 天猫宣布投入2亿元启动持续整个夏季的促销活动,京东拿出5亿元对家电产品进行促销,苏宁易购投入10亿货源降价30%后,最新加入战局的易迅网则将联合腾讯投入5亿元的让利额度及营销资源到3C数码与电器商品上,各家投入金额合计已超过20亿.(来源:

史上最全的机器学习资料(下)

推荐:史上最全的机器学习资料(上) 机器学习(Machine Learning, ML)是一门多领域交叉学科,涉及概率论.统计学.逼近论.凸分析.算法复杂度理论等多门学科.专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能.机器学习牵涉的编程语言十分之广,包括了MATLAB.Julia.R.Perl.Python.Clojure.Ruby等等. 为了让开发者更加广泛.深入地了解机器学习,组织翻译了GitHub Awesome Machi

史上最全的机器学习资料(上)

机器学习(Machine Learning, ML)是一门多领域交叉学科,涉及概率论.统计学.逼近论.凸分析.算法复杂度理论等多门学科.专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能.机器学习牵涉的编程语言十分之广,包括了MATLAB.Julia.R.Perl.Python.Clojure.Ruby等等. 为了让开发者更加广泛.深入地了解机器学习,组织翻译了GitHub Awesome Machine Learning 资源,涵盖2

史上最全编程语言列表,你掌握了哪些?

计算机编程语言可用于将指令传达给计算机.下面可能是史上最全编程语言列表,我将它们分为以下几类,你掌握了哪些? 解释型编程语言 函数式编程语言 编译型编程语言 过程式编程语言 脚本编程语言 标记编程语言 基于逻辑的编程语言 并发编程语言 面向对象编程语言 解释型编程语言 解释型语言是这样一种编程语言,其大部分实现直接执行指令,而无需先将程序编译成机器语言指令.解释器直接执行程序,它会将每个语句翻译成已编译成了机器代码的一个或多个子程序的序列.(维基百科) APL APL 以<编程语言(A Prog

史上最全网站降权原因解析

在互联网时代飞速发展的前景下加上昂贵的竞价费用让多数企业苦不堪言于是低成本投入的职业-seo就诞生了,从业人数也每年呈递增状态,然而互联网的垃圾信息也越来越多,于是搜索引擎为了更好的迎合用户体验也频繁的展开了各项算法调整.在这期间当然也有很多网站被降权甚至被K掉,至于什么原因,有很多从职朋友还不能彻底的找到问题的根源,下面笔者以个人多年经验为大家展开一下分享,希望能帮助你早日恢复网站排名. 直接进入正题: 一.空间问题 因为空间访问速度不稳定,有时慢有时打不开的情况,因这个被降权的案列也是很多的

史上最牛的五次黑客攻击

好莱坞认为,黑客就像是使用计算机的黑魔导士.在电影中,计算机可以炸毁房屋.关闭公路.释放瘟疫还有引发女权运动.也许有人认为,好莱坞的想象力很丰满,但现实是骨感的.他们错了,因为在现实中,确实也有如电影所描述的黑客行动,而且刺激程度绝对不亚于电影.本文中笔者总结出史上最牛的五次黑客攻击. 1.逻辑炸弹引爆西伯利亚 在1982年,里根政府的CIA发现了克格勃(前苏联著名情报机构)从西方窃取技术已经很多年了.对此,美国中央情报局决定给克格勃设一个巨大的陷阱,而这个陷阱很有可能是历史上第一次使用的木马病