Masonry介紹與使用實踐:快速上手Autolayout
http://www.cocoachina.com/ios/20141219/10702.html
1 Masonry開發(fā)技巧
1.1 簡單使用技巧
1.1.1 [基礎(chǔ)]居中顯示一個view
-?(void) viewDidLoad
{
????[super?viewDidLoad];
????//?Do?any?additional?setup?after?loading?the?view.
????WS(ws);
????UIView?*sv?=?[UIView?new];
????[sv?showPlaceHolder];
????sv.backgroundColor?=?[UIColor?blackColor];
????[self.view?addSubview: sv];
????[sv?mas_makeConstraints:^(MASConstraintMaker?*make)?{
????????make.center.equalTo(ws.view);
????????make.size.mas_equalTo(CGSizeMake(300,?300));
????}];
}
代碼效果

????????使用我之間寫的MMPlaceHolder 可以看到superview已經(jīng)按照我們預(yù)期居中并且設(shè)置成了適當(dāng)?shù)拇笮。敲聪瓤纯催@幾行代碼。
//從此以后基本可以拋棄CGRectMake了
UIView?*sv?=?[UIView?new];
//在做autoLayout之前?一定要先將view添加到superview上?否則會報錯
[self.view?addSubview: sv];
//mas_makeConstraints就是Masonry的autolayout添加函數(shù)?將所需的約束添加到block中行了
[sv?mas_makeConstraints:^(MASConstraintMaker?*make)?{
????//將sv居中(很容易理解吧?)
????make.center.equalTo(ws.view);
????//將size設(shè)置成(300,300)
????make.size.mas_equalTo(CGSizeMake(300,?300));
}];
這里有兩個問題要分解一下,首先在Masonry中能夠添加autolayout約束有三個函數(shù)
-?(NSArray?*)mas_makeConstraints: (void(^)(MASConstraintMaker?*make))block;
-?(NSArray?*)mas_updateConstraints: (void(^)(MASConstraintMaker?*make))block;
-?(NSArray?*)mas_remakeConstraints: (void(^)(MASConstraintMaker?*make))block;
/*
mas_makeConstraints?只負(fù)責(zé)新增約束?Autolayout不能同時存在兩條針對于同一對象的約束?否則會報錯;
mas_updateConstraints?針對上面的情況?會更新在block中出現(xiàn)的約束?不會導(dǎo)致出現(xiàn)兩個相同約束的情況;
mas_remakeConstraints?則會清除之前的所有約束?僅保留最新的約束;
三種函數(shù)善加利用?就可以應(yīng)對各種情況了
*/
????????其次 equalTo 和 mas_equalTo的區(qū)別在哪里呢? 其實 mas_equalTo是一個MACRO
#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__)))
????????可以看到 mas_equalTo只是對其參數(shù)進(jìn)行了一個BOX操作(裝箱) MASBoxValue的定義具體可以看看源代碼 太長就不貼出來了。
????????所支持的類型 除了NSNumber支持的那些數(shù)值類型之外 就只支持CGPoint CGSize UIEdgeInsets
介紹完這幾個問題 我們就繼續(xù)往下了 PS:剛才定義的sv會成為我們接下來所有sample的superView
1.1.2 [初級]讓一個view略小于其superView(邊距為10)
UIView?*sv1?=?[UIView?new];
[sv1?showPlaceHolder];
sv1.backgroundColor?=?[UIColor?redColor];
[sv?addSubview: sv1];
[sv1?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(10,?10,?10,?10));
????/*?等價于
????make.top.equalTo(sv).with.offset(10);
????make.left.equalTo(sv).with.offset(10);
????make.bottom.equalTo(sv).with.offset(-10);
????make.right.equalTo(sv).with.offset(-10);
????*/
????/*?也等價于
????make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10,?10,?10,?10));
????*/
}];
????????代碼效果

????????可以看到 edges 其實就是top, left, bottom, right的一個簡化,分開寫也可以 一句話更省事。那么為什么bottom和right里的offset是負(fù)數(shù)呢? 因為這里計算的是絕對的數(shù)值,計算的bottom需要小魚sv的底部高度,所以要-10 同理用于right。
????????這里有意思的地方是and和with,其實這兩個函數(shù)什么事情都沒做。
-?(MASConstraint?*)?with{
????return?self;
}
-?(MASConstraint?*) and?{
????return?self;
}
????????但是用在這種鏈?zhǔn)秸Z法中,就非常的巧妙和易懂 不得不佩服作者的心思(雖然我現(xiàn)在基本都會省略)。
1.1.3 [初級]讓兩個高度為150的view垂直居中且等寬且等間隔排列 間隔為10(自動計算其寬度)
int?padding1?=?10;
[sv2?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.centerY.mas_equalTo(sv.mas_centerY);
????make.left.equalTo(sv.mas_left).with.offset(padding1);
????make.right.equalTo(sv3.mas_left).with.offset(-padding1);
????make.height.mas_equalTo(@150);
????make.width.equalTo(sv3);
}];
[sv3?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.centerY.mas_equalTo(sv.mas_centerY);
????make.left.equalTo(sv2.mas_right).with.offset(padding1);
????make.right.equalTo(sv.mas_right).with.offset(-padding1);
????make.height.mas_equalTo(@150);
????make.width.equalTo(sv2);
}];
????代碼效果

????????這里我們在兩個子view之間互相設(shè)置的約束,可以看到他們的寬度在約束下自動的被計算出來了。
1.1.4 [中級]在UIScrollView順序排列一些view并自動計算contentSize
UIScrollView?*scrollView?=?[UIScrollView?new];
scrollView.backgroundColor?=?[UIColor?whiteColor];
[sv?addSubview: scrollView];
[scrollView?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(5,5,5,5));
}];
UIView?*container?=?[UIView?new];
[scrollView?addSubview: container];
[container?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.edges.equalTo(scrollView);
????make.width.equalTo(scrollView);
}];
int?count?=?10;
UIView?*lastView?=?nil;
for(?int?i?=?1?;?i?<=?count?;?++i?)
{
????UIView?*subv?=?[UIView?new];
????[container?addSubview: subv];
????subv.backgroundColor?=?[UIColor?colorWithHue:(?arc4random()?%?256?/?256.0?) saturation:(?arc4random()?%?128?/?256.0?)?+?0.5 brightness: (?arc4random()?%?128?/?256.0?)?+?0.5 alpha: 1];
????[subv?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????make.left.and.right.equalTo(container);
????????make.height.mas_equalTo(@(20*i));
????????if(?lastView?)
????????{
????????????make.top.mas_equalTo(lastView.mas_bottom);
????????}
????????else{
????????????make.top.mas_equalTo(container.mas_top);
????????}
????}];
????lastView?=?subv;
}
[container?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.bottom.equalTo(lastView.mas_bottom);
}];
????頭部效果:

????尾部效果:

????????從scrollView的scrollIndicator可以看出 scrollView的內(nèi)部已如我們所想排列好了。這里的關(guān)鍵就在于container這個view起到了一個中間層的作用,能夠自動的計算uiscrollView的contentSize。
1.1.5 [高級]橫向或者縱向等間隙的排列一組view
????????很遺憾 autoLayout并沒有直接提供等間隙排列的方法(Masonry的官方demo中也沒有對應(yīng)的案例) 但是參考案例3 我們可以通過一個小技巧來實現(xiàn)這個目的,為此我寫了一個Category。
@implementation?UIView(Masonry_LJC)
-?(void)?distributeSpacingHorizontallyWith: (NSArray*)views
{
????NSMutableArray?*spaces?=?[NSMutableArray?arrayWithCapacity: views.count+1];
????for(?int?i?=?0?;?i?<?views.count+1?;?++i?)
????{
????????UIView?*v?=?[UIView?new];
????????[spaces?addObject: v];
????????[self?addSubview: v];
????????[v?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????????make.width.equalTo(v.mas_height);
????????}];
????}
????UIView?*v0?=?spaces[0];
????__weak?__typeof(&*self)ws?=?self;
????[v0?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????make.left.equalTo(ws.mas_left);
????????make.centerY.equalTo(((UIView*)views[0]).mas_centerY);
????}];
????UIView?*lastSpace?=?v0;
????for(?int?i?=?0?;?i?<?views.count;?++i?)
????{
????????UIView?*obj?=?views[i];
????????UIView?*space?=?spaces[i+1];
????????[obj?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????????make.left.equalTo(lastSpace.mas_right);
????????}];
????????[space?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????????make.left.equalTo(obj.mas_right);
????????????make.centerY.equalTo(obj.mas_centerY);
????????????make.width.equalTo(v0);
????????}];
????????lastSpace?=?space;
????}
????[lastSpace?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????make.right.equalTo(ws.mas_right);
????}];
}
-?(void)?distributeSpacingVerticallyWith: (NSArray*)views
{
????NSMutableArray?*spaces?=?[NSMutableArray?arrayWithCapacity: views.count+1];
????for(?int?i?=?0?;?i?<?views.count+1?;?++i?)
????{
????????UIView?*v?=?[UIView?new];
????????[spaces?addObject: v];
????????[self?addSubview: v];
????????[v?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????????make.width.equalTo(v.mas_height);
????????}];
????}
????UIView?*v0?=?spaces[0];
????__weak?__typeof(&*self)ws?=?self;
????[v0?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????make.top.equalTo(ws.mas_top);
????????make.centerX.equalTo(((UIView*)views[0]).mas_centerX);
????}];
????UIView?*lastSpace?=?v0;
????for(?int?i?=?0?;?i?<?views.count;?++i?)
????{
????????UIView?*obj?=?views[i];
????????UIView?*space?=?spaces[i+1];
????????[obj?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????????make.top.equalTo(lastSpace.mas_bottom);
????????}];
????????[space?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????????make.top.equalTo(obj.mas_bottom);
????????????make.centerX.equalTo(obj.mas_centerX);
????????????make.height.equalTo(v0);
????????}];
????????lastSpace?=?space;
????}
????[lastSpace?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????????make.bottom.equalTo(ws.mas_bottom);
????}];
}
@end
????????簡單的來測試一下
UIView?*sv11?=?[UIView?new];
UIView?*sv12?=?[UIView?new];
UIView?*sv13?=?[UIView?new];
UIView?*sv21?=?[UIView?new];
UIView?*sv31?=?[UIView?new];
sv11.backgroundColor?=?[UIColor?redColor];
sv12.backgroundColor?=?[UIColor?redColor];
sv13.backgroundColor?=?[UIColor?redColor];
sv21.backgroundColor?=?[UIColor?redColor];
sv31.backgroundColor?=?[UIColor?redColor];
[sv?addSubview: sv11];
[sv?addSubview: sv12];
[sv?addSubview: sv13];
[sv?addSubview: sv21];
[sv?addSubview: sv31];
//給予不同的大小?測試效果
[sv11?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.centerY.equalTo(@[sv12,sv13]);
????make.centerX.equalTo(@[sv21,sv31]);
????make.size.mas_equalTo(CGSizeMake(40,?40));
}];
[sv12?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.size.mas_equalTo(CGSizeMake(70,?20));
}];
[sv13?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.size.mas_equalTo(CGSizeMake(50,?50));
}];
[sv21?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.size.mas_equalTo(CGSizeMake(50,?20));
}];
[sv31?mas_makeConstraints: ^(MASConstraintMaker?*make)?{
????make.size.mas_equalTo(CGSizeMake(40,?60));
}];
[sv?distributeSpacingHorizontallyWith: @[sv11,sv12,sv13]];
[sv?distributeSpacingVerticallyWith: @[sv11,sv21,sv31]];
[sv?showPlaceHolderWithAllSubviews];
[sv?hidePlaceHolder];
????代碼效果:

????????perfect!簡潔明了的達(dá)到了我們所要的效果。
1.2 高級開發(fā)技巧
1.2.1 更新layout
1.2.1.1 mas_updateConstraints
????????使用Masonry創(chuàng)建constraint來定義布局的方式有三種:mas_makeConstraints mas_updateConstraints,mas_remakeConstraints。
?????? 有時你需要更新constraint(例如,動畫和調(diào)試)而不是創(chuàng)建固定constraint,可以使用mas_updateConstraints方法。
1.2.1.2 mas_remakeConstraints
??? mas_remakeConstraints與mas_updateConstraints比較相似,都是更新constraint。不過,mas_remakeConstraints是刪除之前constraint,然后再添加新的constraint(適用于移動動畫);而mas_updateConstraints只是更新constraint的值。
1.2.1.3 代碼示例
??? [_loginButton mas_remakeConstraints: ^(MASConstraintMaker*make) {
??????? make.width.equalTo(weakSelf.loginNameTextField.mas_width);
??????? make.height.equalTo(weakSelf.loginNameTextField.mas_height);
??????? make.centerX.mas_equalTo(weakSelf.loginContainerView.mas_centerX);
??????? if (_loginVerifyImageCodeTextField) {
??????????? make.top.equalTo(weakSelf.loginVerifyImageCodeTextField.mas_bottom).with.offset(20);
??????? }
??? }];
??? [_loginDescLabel mas_updateConstraints: ^(MASConstraintMaker*make) {
??????? make.top.equalTo(weakSelf.loginButton.mas_bottom).with.offset(5);
??? }];
1.3 常見問題
1.3.1 在TableCell單元格中無法使用Masonry
????????在TableCell中增加子控件時,無法正常使用Masonry來進(jìn)行布局,可能是Cell重用引起的。
1.3.2 使用了Masonry布局的控件最好手動釋放內(nèi)存,若有APP自動釋放內(nèi)存,會有延遲,導(dǎo)致頁面布局失敗
//需要手動釋放mas_makeConstraints的內(nèi)存,若有APP自動釋放內(nèi)存,會有延遲,導(dǎo)致頁面布局失敗
??? _loginNameTextField = nil;
??? _loginVerifyCodeGetButton = nil;
??? _loginVerifyCodeTextField = nil;
1.3.3 盡量直接用equalTo,而不用mas_equalTo
????????mas_equalTo需要等控件自身布局完成了之后才能調(diào)用,而equalTo不需要。因此進(jìn)行子視圖布局時,用前者容易掛機(jī)。
1.3.4 使用dispatch_get_main_queue保證布局與后續(xù)處理的同步
????????雖然mas_updateConstraints的block回調(diào)是順序執(zhí)行的,但是布局真正起作用還是放在了main_queue的下一個runloop中執(zhí)行的,所以如果要在布局完成后順序執(zhí)行某些處理(例如開始進(jìn)行自定義繪制或者異步繪制),最好手動放到main_queue的下一個runloop中執(zhí)行,確保布局完成后才開始執(zhí)行代碼。
????????如下是示例代碼:
- (void) draw
{
??? [self setLayout];
??? //因為Layout庫是異步的,為了保證執(zhí)行順序上的同步,需要作此處理
??? dispatch_async(dispatch_get_main_queue(), ^{
??????? dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
??????????? CGRectdrawFrame = CGRectMake(0, 0, SCREEN_BOUNDS.size.width, _headerHeight);
??????????? UIGraphicsBeginImageContextWithOptions(drawFrame.size, YES, 0);
??????????? CGContextRefcontext = UIGraphicsGetCurrentContext();
??????????? if(!context) {
??????????????? return;
??????????? }
??????????? [[UIColor whiteColor] set];
??????????? CGContextFillRect(context, drawFrame);
??????????? [_separationLineView drawRect: CGRectMake(0, 0, drawFrame.size.width, _lineHeight)];
??????????? [_iconButton drawRect: _iconButton.frame];
??????????? [_nameButton drawRect: _nameButton.frame];
??????????? [_dateLabel drawRect: _dateLabel.frame];
??????????? UIImage*tempImage = UIGraphicsGetImageFromCurrentImageContext();
??????????? UIGraphicsEndImageContext();
??????????? dispatch_async(dispatch_get_main_queue(),^{
??????????????? [_iconButton setHidden:NO];
??????????????? [_nameButton setHidden:NO];
??????????????? [_dateLabel setHidden:NO];
??????????????? _bgImageView.image = nil;
??????????????? _bgImageView.image=tempImage;
??????????? });
??????? });
??? });
}
2 參考鏈接
實時顯示iOS編寫UI代碼效果
http://blog.csdn.net/zhang_red/article/details/45503683
如何使用masonry設(shè)計復(fù)合型cell(約束優(yōu)先級使用)