如何优雅的代码编写 AutoLayout

概述

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

  1. Align(对齐)

    Align(对齐)

  2. Pin(相对)

    Pin(相对)

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

    Resolve Auto Layout Issues(约束处理)

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

不忘初心,方得始终

Talk is cheap, show me the code

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

  1. 利用 NSLayoutConstraint 类创建具体的约束对象;
  2. 添加约束对象到相应的 view 上,代码有这两种:
- (void)addConstraint:(NSLayoutConstraint *)constraint;
- (void)addConstraints:(NSArray *)constraints;

或许有人问了,原来才两个步骤就可以了,我刚刚裤子都脱了,你就给我看这个?!
话不多说,马上 show you the code !
先看看我们使用 frame 的方式是如何确定一个 view 的位置的:

- (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 的实现:

- (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];
}

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

  1. 创建约束对象(NSLayoutConstraint)的常用方法
    一个 NSLayoutConstraint 对象就代表一个约束。
+ (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 的核心计算公式:

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:

    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 的:

#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 开车,赶紧上车

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

    // 长宽均为 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);
    }];

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

    // 长宽均为 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,粘着父 view 右下角,间距为 16

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

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" 前面。

//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

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

- (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 平分屏幕宽度,带有间隙

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

- (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);
    }];
}

文/angelen(简书作者)
原文链接:http://www.jianshu.com/p/3c7e202c76b2
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

时间: 2024-08-30 04:02:48

如何优雅的代码编写 AutoLayout的相关文章

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

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

正确理解Java代码编写规范

本文从Java代码编写的初期到结尾,做了一次整体的总结,希望对初学者有帮助. 1.命名很重要 一个错误的命名会很误导人,不良的命名,对于阅读代码的人来说很纠结.一个良好的命名对自己也有很大的帮助. 我个人命名的变量都比较长,一般是单词的全称,这样代码读起来易懂,有些缩写你根本不知道它代表的单词是什么,除了像id代表identifier,org代表organization这些大家常见的缩写命名. 命名一个方法时候,最好能让大家见名知意,看到名字就能猜出你的功能,而不需要去看方法的注释,甚至是读源码

大师传承-java代码编写的30条建议

成为一个优秀的Java程序员,有着良好的代码编写习惯是必不可少的.下面就让我们来看看代码编写的30条建议吧. (1) 类名首字母应该大写.字段.方法以及对象(句柄)的首字母应小写.对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母.例如: ThisIsAClassName thisIsMethodOrFieldName 若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母.这样便可标志出它们属于编译期的常数. Java包(Package

JAVA代码编写的30条建议

列举了大量有用的建议,帮助大家进行低级程序设计,并提供了代码编写的一般性指导: (1) 类名首字母应该大写.字段.方法以及对象(句柄)的首字母应小写.对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母.例如: ThisIsAClassName thisIsMethodOrFieldName 若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母.这样便可标志出它们属于编译期的常数. Java包(Package)属于一种特殊情况:它们全都是

为什么谷歌要执行严格的代码编写规范(转)

我们在谷歌所做事情中另外一个让我感到异常有效.有用的制度是严格的编码规范. 在到Google工作之前,我一直认为编码规范没有什么用处.我坚信这些规范都是官僚制度下产生的浪费大家的编程时间.影响人们开发效率的东西. 我是大错特错了. 在谷歌,我可以查看任何的代码,进入所有谷歌的代码库,我有权查看它们.事实上,这种权限是很少人能拥有的.但是,让我感到惊讶的却是,如此多的编码规范-缩进,命名,文件结构,注释风格-这一切让我出乎意料的轻松的阅读任意一段代码,并轻易的看懂它们.这让我震惊-因为我以为这些规

HTML 代码编写的30条技巧

本文总结了30条HTML代码编写指南,只要在编写HTML代码的过程中牢记它们,灵活运用,你一定会写出一手漂亮的代码,早日迈入专业开发者的行列. 1. 一定要闭合HTML标签 在以往的页面源代码里,经常看到这样的语句: <li>Some text here. <li>Some new text here. <li>You get the idea. 也许过去我们可以容忍这样的非闭合HTML标签,但在今天的标准来看,这是非常不可取的,是必须百分百避免的.一定要注意闭合你的H

HTML和CSS的代码编写规范

在很多开发人员眼里,编码HTML简直容易极了,编写CSS不但简单有时还会显得很繁琐-相同的属性得一个劲不停地写.为此,曾经自己也迷惑过也遇到过不少问题,但随着写&读的前端代码渐渐增多,慢慢体会到,“能写”和“会写”之间还是有一定距离的.很多时候,你可以“这样做”,但并不意味着“你应该”这么做. 合理地编写HTML和CSS,可以让代码看起来更专业.即便是很简单的几行代码,也要写的有性格.嗯~用饱含工匠精神的态度去写码,你一定会在苦逼中作乐的. 以下整理些从别人那读到学到的,同时自己认可的琐碎的点,

这些HTML、CSS知识点,面试和平时开发都需要 No10-No11(知识点:表格操作、代码编写规则)

系列知识点汇总 1.基础篇 这些HTML.CSS知识点,面试和平时开发都需要 No1-No4(知识点:HTML.CSS.盒子模型.内容布局) 这些HTML.CSS知识点,面试和平时开发都需要 No5-No7(知识点:文字设置.设置背景.数据列表) 这些HTML.CSS知识点,面试和平时开发都需要 No8-No9(知识点:媒体操作.构建表单) 这些HTML.CSS知识点,面试和平时开发都需要 No10-No11(知识点:表格操作.代码编写规则) 2.进阶篇 如何提升我的HTML&CSS技术,编写有

Python——类代码编写细节

类代码编写细节 继续学习类.方法和继承. ================================================================================ class语句 以下是class语句的一般形式: class <name>(superclass,...): data = value def method(self,...): self.member = value 在class语句内,任何赋值语句都会产生类属性,而且还有特殊名称方法重载运