<code>Masonry</code>源碼閱讀
<strong>閱讀源碼是一種美妙的體驗</strong>
這么強大的布局庫,就不做解釋了。因為系統的自動布局寫起來很麻煩,所以 Masonry 成了當前流行的使用代碼布局的方式(當然是在OC中)
具體使用如下:
[self.headView addSubview:self.headLabel];
[self.headLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-2);
make.left.mas_equalTo(13);
make.height.mas_equalTo(15);
}];
<code>Masonry</code> 也是支持鏈式調用的。
view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
不做解釋了。
<strong>閱讀源碼是重點</strong>
<code>Masonry</code> 在 手機 開發 和 Mac 開發都能用,所以宏定義了
#if TARGET_OS_IPHONE || TARGET_OS_TV
#import <UIKit/UIKit.h>
#define MAS_VIEW UIView
#define MAS_VIEW_CONTROLLER UIViewController
#define MASEdgeInsets UIEdgeInsets
typedef UILayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
#elif TARGET_OS_MAC
#import <AppKit/AppKit.h>
#define MAS_VIEW NSView
#define MASEdgeInsets NSEdgeInsets
typedef NSLayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;
#endif
根據平臺的不同,重新定義了 MAS_VIEW 和 MASEdgeInsets
在布局之前,內部代碼會自動加上<code>self.translatesAutoresizingMaskIntoConstraints = NO;</code>,所以不用我們外部加。
跟一個布局流程,看看內部怎樣做的。
內部定義了一個 <code> MAS_VIEW </code> 的 分類,這樣子我們在外部,只要是<code>UIView</code>或者 <code>UIView</code>的子類,然后引入頭文件,就可以調用方法了。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
/// 設置自動布局。
self.translatesAutoresizingMaskIntoConstraints = NO;
/// 生成 MASConstraintMaker
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
/// 執行外部的block
block(constraintMaker);
/// 安裝布局信息
return [constraintMaker install];
}
<code> mas_makeConstraints </code> 添加布局信息
- 生成<code> MASConstraintMaker</code>
- <code>block(constraintMaker)</code>把上一步生成的對象作為參數。然后調用外面的<code>block</code>,重點是設置 上一步生成對象的屬性
- 讓布局信息生效
<strong>接下來,我們的注意力只要轉移到<code> MASConstraintMaker</code> 就行了</strong>
生成 <code>MASConstraintMaker</code>對象的過程比較簡單
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
- 用 view 初始化一個對象 這里 view 的引用是 <code>weak</code>的。
- 創建一個 <code> self.constraints </code>數組,字面意思理解,是存放所有約束的。我們大膽猜測,跟鏈式調用,是有關系的。
<strong><code>install</code></strong>的過程也簡單,
- (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;
}
- 判斷要不要刪除之前已經存在的約束 <code>removeExisting</code>,如果要刪除,則調用 <code> MASConstraint 的 uninstall </code>方法卸載掉約束 <code> removeExisting </code>只有在 <code>mas_remakeConstraints</code>的時候才為 <code>YES</code>
- 遍歷 <code>self.constraints</code> 然后調用 <code> MASConstraint 的 install </code>方法,讓約束生效
- 清空 <code> self.constraints </code>數組,因為保存的約束都已經生效了。
<strong>接下來只要分析<code> MASConstraint </code> 思路就清楚了</strong>
<code>MASConstraint</code>是MAS庫,封裝的,關于約束的一個類,外面能夠進行鏈式調用的設置約束,也是得益于<code>MASConstraint</code>。定義了很多常用的約束操作。比如:
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;
~~~~~ 還有N多個操作。
上面的函數都返回自身對象,所以才能夠進行鏈式調用。
我們上面的例子中
block 執行了
make.bottom.mas_equalTo(-2);
make.left.mas_equalTo(13);
make.height.mas_equalTo(15);
因為<code> bottom left height </code>屬性都是lazy load 的,所以只要調用了,就會添加一個約束給 view
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
最終會調用 ps(我們標記一下這個函數 為 <strong>S</strong>)
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
/// 生成 MASViewAttribute MASViewAttribute 是用來描述一個view 和 約束的 關系
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
/// 生成 MASViewConstraint MASViewConstraint 是 MAS描述約束的。
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
/// 我們先記下S這里的作用,以后看看作用,
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;
}
if (!constraint) {
newConstraint.delegate = self;
/// 添加到約束數組中
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
到這一步,可以看到<code>self.constraints</code>中存的是<code>MASViewConstraint</code> 對象,并且把代理指向了<code> maker </code>
設置約束的值,一般使用已經定義好的宏
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
#endif
如果不想使用 <code>mas</code>開頭的宏,可以在全局定義 <code> MAS_SHORTHAND_GLOBALS </code> 比如:
#define MAS_SHORTHAND_GLOBALS
就可以了
所以,類似
栗子 <strong>A</strong>
UIButton *button = [UIButton new];
[self.view addSubview:button];
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10);
make.top.mas_equalTo(10);
make.size.mas_equalTo(CGSizeMake(20, 20));
}];
中設置 <code>left.mas_offset(10);</code>左邊距為10 最后就是調用,<code>MASConstraint</code>的
- (MASConstraint * (^)(NSValue *value))valueOffset {
return ^id(NSValue *offset) {
NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
[self setLayoutConstantWithValue:offset];
return self;
};
}
函數,函數同樣是返回一個 <code>block</code>,并且 <code>block</code>中返回<code>self</code>方便鏈式調用.
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *)value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
<code> setLayoutConstantWithValue </code>函數中,會判斷設置的值得類型,然后進行不同的設置,根據約束的不同 比如:<code>NSLayoutAttributeWidth NSLayoutAttributeLeft</code> 等等。。不做多余解釋,
跟 <strong>栗子 A</strong> 有同樣作用的鏈式調用,可以這樣子寫。
<strong> 栗子 B</strong>
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10).top.mas_equalTo(10);
make.size.mas_equalTo(CGSizeMake(20, 20));
}];
不同之處在于
make.left.mas_offset(10).top.mas_equalTo(10);
第一個 <code>.left</code> 是 <code> MASConstraintMaker </code>中的屬性,調用完成后會返回 自己。 第二個 <code>top</code> 是 <code>MASViewConstraint</code> 自己的。下面分析一下,這個怎么玩的。
你看,調用 .top 的時候,<code>MASViewConstraint</code> 沒有做什么事情,直接調用 <code>delegate</code> 的 <code>[constraint:addConstraintWithLayoutAttribute:];</code>方法
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
峰回路轉,調用到我們剛才標記的 <strong>S</strong> 那里了,這回,第一個參數有值了。我們可以揭開神秘的面紗了。
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;
}
- 首先生成<code> MASCompositeConstraint </code> 對象。
- 然后把 對象的代理設置為 <code>self</code>
- 然后替換 傳進來的第一個參數在<code> constraints</code> 中的值。
- 最后返回 對象,因為<code> MASCompositeConstraint</code> 繼承自 <code> MASConstraint </code> 所以后面的鏈式調用也是可以繼續的。
替換函數如下
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
之所以要替換,是因為 防止在最后 遍歷 <code> self.constraints </code>設置約束的時候重復 因為 <code>MASCompositeConstraint</code> 也是繼承自 <code>MASConstraint</code> 和 <code>MASViewConstraint</code> 一樣 實現了很多之后要用到的方法。比如 <code>install</code> 等等。
<code>MASConstraint</code> 定義了約束 操作的函數,但是對約束的操作都放在子類中進行
- <code>MASViewConstraint</code>一個單獨的約束
- <code>MASComposisteConstraint</code>一組約束
<code>MASConstraint</code>中需要子類重寫的方法,都拋出了異常
#define MASMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]
<code>MASViewConstraint</code> 單獨的約束,重寫了 <code>MASConstraint</code> 標記的需要重寫的方法。在父類鏈式調用的基礎上,增加了。
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
用來描述跟約束相關的信息。
<code> MASCompositeConstraint </code> 代表一組約束。內部存放了一個私有變量
@property (nonatomic, strong) NSMutableArray *childConstraints;
用來存放單個的約束的信息,一般都是存放 <code>MASViewConstraint</code>.
mas_makeConstraints
mas_updateConstraints
mas_remakeConstraints
函數的區別:
<code>updateConstraints</code> 函數里里會設置<code>updateExisting</code>為 <code>YES</code>
<code>remakeConstraints</code> 函數里會設置<code>removeExisting = YES;</code>
如果 <code>updateExisting</code>為<code> YES </code>的時候再一個約束<code>install</code>的時候會先尋找當前<code>view</code> 有沒有相同的約束,如果有,就直接更新約束的 <code>constant</code>值。代碼如下:
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
}
如果 <code>removeExisting = YES;</code> 在 <code>install</code>之前,會刪除<code>view</code>的所有約束
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
<strong> Masonry </strong>這個經常使用庫我們就摸到了冰山一角了。以后有空一行一行的扣一下細節