MyException - 我的异常网
当前位置:我的异常网» 移动开发 » 史上比较用意的纯代码实现 AutoLayout

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

www.MyException.Cn  网友分享于:2013-08-22  浏览:0次
史上比较用心的纯代码实现 AutoLayout
概述

使用 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 上,代码有这两种:
- (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];
}

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

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

添加约束(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:
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 的:
#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 右下角,间距为 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,这样它就自动帮你包装好了。
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);
    }];
}

总结

其实 Masonry 的文档已经很详细了,建议大家去看文档

文章评论

10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
程序员必看的十大电影
程序员必看的十大电影
那些争议最大的编程观点
那些争议最大的编程观点
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
10个调试和排错的小建议
10个调试和排错的小建议
老程序员的下场
老程序员的下场
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
程序员都该阅读的书
程序员都该阅读的书
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
每天工作4小时的程序员
每天工作4小时的程序员
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
旅行,写作,编程
旅行,写作,编程
 程序员的样子
程序员的样子
编程语言是女人
编程语言是女人
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
代码女神横空出世
代码女神横空出世
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
为什么程序员都是夜猫子
为什么程序员都是夜猫子
鲜为人知的编程真相
鲜为人知的编程真相
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
Java程序员必看电影
Java程序员必看电影
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
如何成为一名黑客
如何成为一名黑客
漫画:程序员的工作
漫画:程序员的工作
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
中美印日四国程序员比较
中美印日四国程序员比较
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
我的丈夫是个程序员
我的丈夫是个程序员
程序员的鄙视链
程序员的鄙视链
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
程序员应该关注的一些事儿
程序员应该关注的一些事儿
总结2014中国互联网十大段子
总结2014中国互联网十大段子
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
一个程序员的时间管理
一个程序员的时间管理
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有