Masonry 到底有多美?(下)

作者:伯乐在线 - 小笨狼

链接:http://ios.jobbole.com/83384/

key

当约束冲突发生的时候,我们经常为找不到是哪个View冲突的而烦恼,这一堆View是个什么东西呀?

"<MASLayoutConstraint:0x7f8de483fb10 UIView:0x7f8de2f53870.left == UIView:0x7f8de2f586c0.left>",

"<MASLayoutConstraint:0x7f8de4818b50 UIView:0x7f8de2f53870.right == UIView:0x7f8de2f586c0.right>",

"<MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>",

"<NSLayoutConstraint:0x7f8de4847e90 UIView:0x7f8de2f586c0.width == 375>"

Will attempt to recover by breaking constraint

<MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>

这时候我们可以设置View的key:

self.view.mas_key = @"self.view";

view1.mas_key = @"view1";

设置之后再看一下,哈哈,现在好多了。可以清晰的知道是哪个view了

"<MASLayoutConstraint:0x7fcd98d17c40 UIView:view1.left == UIView:self.view.left>",

"<MASLayoutConstraint:0x7fcd98d2b2c0 UIView:view1.right == UIView:self.view.right>",

"<MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>",

"<NSLayoutConstraint:0x7fcd98e42050 UIView:self.view.width == 375>"

Will attempt to recover by breaking constraint

<MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>

大家可能会觉得这样一个一个设置,多麻烦啊!别着急,Masonry提供了批量设置的宏MASAttachKeys 只需要一句代码即可全部设置:

MASAttachKeys(self.view,view1);

Shorthand(12月7日新增)

在写代码的时候,可能你会感觉有的东西要加mas前缀,有的东西又不用加,代码风格不统一,而且加mas前缀还麻烦。

前面介绍过加mas前缀主要是在扩展系统类的时候为了避免与原有类冲突,这是Apple推荐的做法。不过目前来说,即使不加mas前缀,也不会有什么问题。所以Masonry提供了不加mas_前缀的方法,只需要你定义几个宏即可。

1. MAS_SHORTHAND

定义MASSHORTHAND宏之后。可以使用UIView,NSArray中不带mas前缀的makeConstraints,updateConstraints,remakeConstraints。以及UIView中不带mas_前缀的Attribute。

2. MAS_SHORTHAND_GLOBALS

默认的equalTo方法只接受id类型的对象。有时候我们想传入一个CGFloat, CGSize, UIEdgeInsets等。还需要将其转化成NSValue对象,比较麻烦。Masonry也考虑到了这种情况。只需要定义MAS_SHORTHAND_GLOBALS宏。就可以直接对equalTo传入基础类型。Masonry自动转化成NSValue对象

拨开Masonry的衣服

Masonry的基本使用方法介绍完了,那么我们来看看Masonry的内部到底有些什么东西?

结构

Masonry一共有十三个类,我将这13个类分为5个模块:

Help

Help模块主要是一些辅助的类。

NSLayoutConstraint+MASDebugAdditions:这个类的主要作用是重写NSLayoutConstraint的description函数。让约束发生冲突的时候,更易读。如果View或者constraint设置了Key,直接用key的值显示到description中。如果没有设置,显示View或者constraint的指针。

ViewController+MASAdditions:提供了ViewController的LayoutGuide相关属性,以便View对齐时使用 MASUtilities:定义了一些公用的宏和属性

Shorthand

对于系统原有类(NSArray,UIView)的扩展。Masonry的category方法和属性都加有mas前缀。这也是Apple建议的做法,避免跟系统原有方法冲突。但是有时候我们可能想用的更方便,不想写mas前缀(没办法,我就是这么懒…)

在NSArray+MASShorthandAdditions和View+MASShorthandAdditions中定义了不带mas前缀的扩展。这些扩展根据你是否定义了MAS_SHORTHAND宏来确定是否编译。所以你只需要定义MAS_SHORTHAND宏,就可以方便的使用不带mas前缀的方法,比如:-[view makeConstraints:]

Public

Public模块主要是对外暴露的方法。使用者使用Masonry可以直接接触到。

  • NSArray+MASAdditions:主要有定义和更新约束的方法,如mas_makeConstraints:
  • View+MASAdditions:除了定义和更新约束的一系列方法之外,还为View增加了mas_top, mas_left等Attribute属性

Core

Core模块就是Masonry的核心部分,Masonry的大部分功能都在这4个类里实现

  • MASConstraintMaker:约束控制器。控制更新,删除,或者新增约束
  • MASConstraint:约束的基类,虚类。定义了Constraint的基本属性和方法。
  • MASViewConstraint: 约束的主要实现类。所有对约束使用的功能均在此类中完成
  • MASCompositeConstraint:约束的集合类。内部有一个数组,可以保存多个MASViewConstraint。对MASCompositeConstraint调用方法实际等于对其内部的所有MASViewConstraint调用方法

Property

此模块主要封装了一些MASConstraint持有的属性。为了使用更方便,或者扩展功能

  • MASViewAttribute:每一个Attribute都有一个View与之对应,为了使用更方便,所以将他们通过一个类封装在一起
  • MASLayoutConstraint:默认的NSLayoutConstraint是没有Key这个属性的,为了Debug方便。派生一个子类,持有key属性

实现

当我们给View添加一个约束的时候到底发生了什么?

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {

make.left.top.equalTo(view1.superview).offset(20);

}];

我们首先来看make.left.top.equalTo(view1.superview).offset(20);

一、执行”make.left”

MASConstraintMaker类中有一个属性constraints专门用来存储constraint

@property (nonatomic, strong) NSMutableArray *constraints;

当执行make.left的时候, 会将相应的MASConstraint添加到constraints数组中

- (MASConstraint *)left {

return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];

}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {

return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];

}

//核心方法

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {

MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];

MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];

//调用make.left.top时走入这里将原来的ViewConstraint替换成MASCompositeConstraint

if ([constraint isKindOfClass:MASViewConstraint.class]) {

//replace with composite constraint

NSArray *children = @[constraint, newConstraint];

MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];

compositeConstraint.delegate = self;

[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];

return compositeConstraint;

}

// 调用make.left的时候走入这里,将constraint加入到self.constraints中

if (!constraint) {

newConstraint.delegate = self;

[self.constraints addObject:newConstraint];

}

return newConstraint;

}

对MASConstraintMaker调用Attribute的get方法,最终都会走到-constraint:addConstraintWithLayoutAttribute:中,在这个方法中,通过对应的Attribute生成MASViewConstraint。然后将MASViewConstraint加入到constraints中

二、执行”.top”

make.left返回的是MASConstraint类型。所以make.left.top是对MASViewConstraint类型调用top方法。

- (MASConstraint *)top {

return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];

}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {

NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];

}

当执行-addConstraintWithLayoutAttribute的时候,ViewConstraint通过delegate又调回到MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:中。

在MASConstraintMaker的-constraint:addConstraintWithLayoutAttribute:里,将原来constraints中的MASViewConstraint替换成MASCompositeConstraint。MASCompositeConstraint持有top,left 2个属性。对MASCompositeConstraint做操作时候,其内部的所有属性都会执行相应的操作

三、执行”.equalTo(view1.superview)”

 

- (MASConstraint * (^)(id))equalTo {

return ^id(id attribute) {

return self.equalToWithRelation(attribute, NSLayoutRelationEqual);

};

}

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {

return ^id(id attribute, NSLayoutRelation relation) {

if ([attribute isKindOfClass:NSArray.class]) {

NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");

NSMutableArray *children = NSMutableArray.new;

for (id attr in attribute) {

MASViewConstraint *viewConstraint = [self copy];

viewConstraint.secondViewAttribute = attr;

[children addObject:viewConstraint];

}

MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];

compositeConstraint.delegate = self.delegate;

[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];

return compositeConstraint;

} else {

NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");

self.layoutRelation = relation;

self.secondViewAttribute = attribute;

return self;

}

};

}

 

当执行Relationship的方法时,都会走到-equalToWithRelation中。 在这个方法里面主要是给realationship和secondViewAttribute赋值:

1. 如果不是数组

直接对realationship和secondViewAttribute赋值

2. 如果是数组

如: .equalTo(@[view1.mas_left,view2.mas_left]),逻辑上肯定不能是不等关系(>=,<=),所以realationship不用赋值,使用默认值(=)。copy出多个viewConstraint,将secondViewAttribute赋值。然后用多个viewConstraint组成的compositeConstraint替换调原来的viewConstraint。

四、执行”.offset(10)”

 

- (MASConstraint * (^)(CGFloat))offset {

return ^id(CGFloat offset){

self.offset = offset;

return self;

};

}

- (void)setOffset:(CGFloat)offset {

self.layoutConstant = offset;

}

 

offset(10)会将10传入到ViewConstraint中,用layoutConstant属性将其存起来。(offset主要影响的是约束里面的constant)

五、mas_makeConstraints

看完了make.left.top.equalTo(view1.superview).offset(20);,我们再看看mas_makeConstraints中到底做了什么?

 

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {

self.translatesAutoresizingMaskIntoConstraints = NO;

MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];

block(constraintMaker);

return [constraintMaker install];

}

 

mas_makeConstraints方法很简单

  • 将self.translatesAutoresizingMaskIntoConstraints至为NO。translatesAutoresizingMaskIntoConstraints表示是否将设置的Frame转化为约束。当自己设置约束的时候需要将其置为NO
  • 创建出MASConstraintMaker对象
  • 通过block抛出到外面设值
  • constraintMaker install

上面的代码我们知道,关键的地方还是在于constraintMaker install

六、constraintMaker install

 

- (NSArray *)install {

if (self.removeExisting) {

NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];

for (MASConstraint *constraint in installedConstraints) {

[constraint uninstall];

}

}

NSArray *constraints = self.constraints.copy;

for (MASConstraint *constraint in constraints) {

constraint.updateExisting = self.updateExisting;

[constraint install];

}

[self.constraints removeAllObjects];

return constraints;

}

  • 如果需要removeExisting,就把已有的约束remove掉,当调用mas_remakeConstraints的时候会将removeExisting值置为YES
  • 遍历constraints,调用[constraint install]
  • 清空constraints,这里的constraintMaker只是一个零时属性,只是一个工具类,不需要存储。所以用完之后就可以将constraints清空

其实真正关键的地方在[constraint install]

七、constraint install

- (void)install {

// 1. 已经installed的将不做任何操作

if (self.hasBeenInstalled) {

return;

}

//2. 从ViewAttribute中剥离出item和attribute

MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;

NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;

MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;

NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

//3. 如果没有secondViewAttribute,默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。

if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {

secondLayoutItem = self.firstViewAttribute.view.superview;

secondLayoutAttribute = firstLayoutAttribute;

}

//4. 创建真正用于Autolayout的约束layoutConstraint

MASLayoutConstraint *layoutConstraint

= [MASLayoutConstraint constraintWithItem:firstLayoutItem

attribute:firstLayoutAttribute

relatedBy:self.layoutRelation

toItem:secondLayoutItem

attribute:secondLayoutAttribute

multiplier:self.layoutMultiplier

constant:self.layoutConstant];

//5. 将priority和key赋值

layoutConstraint.priority = self.layoutPriority;

layoutConstraint.mas_key = self.mas_key;

//6. 找到要添加约束的installView

if (self.secondViewAttribute.view) {

MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];

NSAssert(closestCommonSuperview,

@"couldn‘t find a common superview for %@ and %@",

self.firstViewAttribute.view, self.secondViewAttribute.view);

self.installedView = closestCommonSuperview;

} else if (self.firstViewAttribute.isSizeAttribute) {

self.installedView = self.firstViewAttribute.view;

} else {

self.installedView = self.firstViewAttribute.view.superview;

}

//7. 添加或更新约束

MASLayoutConstraint *existingConstraint = nil;

if (self.updateExisting) {

existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];

}

if (existingConstraint) {

// just update the constant

existingConstraint.constant = layoutConstraint.constant;

self.layoutConstraint = existingConstraint;

} else {

[self.installedView addConstraint:layoutConstraint];

self.layoutConstraint = layoutConstraint;

[firstLayoutItem.mas_installedConstraints addObject:self];

}

}

  • 如果已经installed就不做任何操作
  • 从ViewAttribute中剥离出item和attribute。前面我们介绍过MASViewAttribute的类主要是将item和attribute2个属性封装在了一起。
  • secondViewAttribute的值来自于.equalTo(item.attribute)中的item.attribute。当我们写下类似make.left.offset(10);约束的时候,是没有secondViewAttribute的,这时候默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。这就解释了为什么可以这样写make.left.offset(10);
  • 创建真正用于Autolayout的约束layoutConstraint
  • 将priority和key赋值
  • 找到要添加约束的installView。如果是2个View之间的约束,需要寻找这2个View最接近的共同父View。添加约束
  • 添加或更新约束。当调用mas_updateConstraints的时候updateExisting=YES。这时候会查找是否有已经存在的约束。有就更新,没有就添加。如果是mas_makeConstraints或mas_remakeConstraints,则直接添加

Extension

仅仅将代码结构和基本实现过程解析了一下,更多实现细节还需要大家自己去阅读源码

说实话,Masonry的代码写得真漂亮,不管是代码格式规范,还是设计模式。看起来简直是一种享受。建议大家阅读。

参考

  • Masonry源码
  • Autolayout的第一次亲密接触
  • iOS8上关于UIView的Margin新增了3个APIs
时间: 2024-10-14 19:58:16

Masonry 到底有多美?(下)的相关文章

Google Dremel数据模型详解(下)

"神秘"的r和d 单从数据结构来看的话,我们可以这样解释r和d的含义.r代表着当前字段与前一字段的关系,是在哪一层合并的,即公共的父结点在哪?举例来说,假如我们重建到了Code='en',通过r=2可以知道是在Language那一层发生了重复. 为了保持原纪录的结构,我们会保存一些NULL数据,而d就是用于重建NULL字段.通过d的值,就能知道NULL的结构.例如下图,通过r=1知道应该合并到Name那一层.而通过d=1则知道路径上只有一个字段,即不仅仅是Code字段不存在,Langu

令人眼前一亮的下拉式终端 Tilda & Guake

前言 老夫是 Linux 的~~老~~用户. 大一的时候某不方便透露姓名的校内组织给了一个 Fedora 13 的安装光盘,然后老夫学会了重装 Windows . 大二的时候知道了 Ubuntu ,开始在虚拟机和双系统中来回切换. 大三的时候硬盘安装了 CentOS ,开始正式成为 Linux 的用户. 大四的时候被 Debian 俘获. 毕业后的第一年在老板的威逼利诱之下,重新使用 Ubuntu . 现在老夫是 Debian 的忠实用户. 老夫一直在强调是 Linux 的用户,那是因为真的只是

Android ListView下拉/上拉刷新:设计原理与实现

 <Android ListView下拉/上拉刷新:设计原理与实现> Android上ListView的第三方开源的下拉刷新框架很多,应用场景很多很普遍,几乎成为现在APP的通用设计典范,甚至谷歌官方都索性在Android SDK层面支持下拉刷新,我之前写了一篇文章<Android SwipeRefreshLayout:谷歌官方SDK包中的下拉刷新>专门介绍过(链接地址:http://blog.csdn.net/zhangphil/article/details/4696537

历代多少名扬千古美男子?中国古代八大小鲜肉排行榜!

历代多少名扬千古美男子?中国古代八大小鲜肉排行榜!国产光纤涂覆机2018-10-20 11:00:53 人美在骨不在皮,好看的皮囊终究容易逝去,高洁的人格与风骨,才与世长存.历代多少名扬千古美男子?中国古代八大小鲜肉排行榜!第一名:晋代"花县令"潘安: 一千七百年过去了, 若论容貌, 我还是NO1null潘安这个名字注定是和帅气连在一起的.在历代帅哥排行榜上,他绝对是个宗师级别的人物,没有更美,只有最美,旷古绝今.潘安到底有多美? 据史料记载,潘安身材修长(野史称一米八五),眉目如画,

python with原型

@Python 的 with 语句详解 这篇文章主要介绍了Python 的 with 语句,本文详细讲解了with语句.with语句的历史.with语句的使用例子等,需要的朋友可以参考下 一.简介 with是从Python 2.5 引入的一个新的语法,更准确的说,是一种上下文的管理协议,用于简化try…except…finally的处理流程.with通过__enter__方 法初始化,然后在__exit__中做善后以及处理异常.对于一些需要预先设置,事后要清理的一些任务,with提供了一种非常方

SQL语句类别、数据库范式、系统数据库组成

前言 终于等到这一天,我要开始重新系统学习数据库了,关于数据库这块,不出意外的话,每天会定时更新一篇且内容不会包含太多,简短的内容,深入的理解. SQL语句类别 SQL语句包括以下三个类别 (1)数据定义语言(Data Definnition Language)即DDL,我们数据最终从何而来,当然首先必须得建立表,所以它包括CREATE.ALTER.DROP表. (2)数据操作语言(Data Manipulation Language)即DML,我们对数据需要进行什么操作,当然无非就是增删改查,

Markdown,你只需要掌握这几个

为什么使用Markdown?这是一个问题.答案有很多种.比如,不局限于格式啦,比如 .xls 文档得用excel打开吧, .doc 文档得用word打开吧, .xxx 得用xxx打开吧.如果你机子上没有这个软件,岂不是抓瞎了,还能不能愉快的学习了?!因此你需要一种能使用简单的文本编辑器就可以书写,有浏览器就可正常显示的格式或标记,Markdown很适合哦.什么,这个理由不好?那么就权当是装B吧,你说你用Markdown写文档,是不是逼格一下就上去了.(哎,估计也只有我这种新手才用来炫耀,大牛都是

关于这周工作中遇到的关于缓存问题的记录

序:本周在工作中遇到了一些麻烦,解决过程比较曲折和辛苦,特此记录,留作经验供以后参考 发现问题:周一上班的时候,运营打电话来说,我们上个月做的一个活动感觉数据不对,商家过来投诉了.结果我数据库一查,数据还真有问题!这次的活动采用的是页面上使用缓存系统显示活动数值(总金额),同时在后台记录详细的每条活动数据的办法.每次用户发生业务行为的时候都会在后台的缓存的总金额上增加,同时记录这次行为发生的金额数.结果我周一把数据库的记录加一起来一算,发现和页面上缓存的总金额竟然差了将近一半! 解决的过程: 1

建置 POSTFIX 服务器

建置 POSTFIX 服务器 postfix 是除了 sendmail 以外 ,最被广泛采用的 Linux 邮件服务器,一般使用的观感不外乎两点: 一.安全:垃圾信过滤机制较聪明,就算什么都没设定,也能滤掉许多 sendmail 挡不到的信. 二.简单:不需设定,服务器就能正常运作. 虽然以上谈的这两点特色,到底是好是坏还有许多争议,但 Postfix 对于邮件服务需求量不高的校园来说,可以说是一个很好的选择! 本文是假设您已经读过 Linux 进阶班讲义,因此对于 MUA.MTA.MDA.MS