一个朋友说他原本用frame做代码布局,打算试试约束布局,好吧!懒得一一说就默默的写篇文章。。。Masonry相对于原生来说,在代码添加约束上非常强大、实用、简便(想试试原生约束可以看我之前写的一篇帖子代码约束NSLayoutConstraint)。本篇文章就简单说一下Masonry的使用以及也是重点Masonry内存优化(内存优化为后期更新),需要先将Masonry下载并导入项目中Masonry下载地址。废话就不说了,开始来点干货。
Masonry调用方法
新增约束,不管原本是否已经存在约束都新增约束,相对的也会导致增加内存的消耗。
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
更新约束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
清除之前的约束,只保留最新约束。不管原本是否已经存在约束,都先进行清除约束,再新增约束。
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
Masonry使用
举个栗子,用栗子来说明:
[_logoView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.mas_equalTo(0);
make.size.mas_equalTo(CGSizeMake(MAINSIZE.width, SCR_W * 278));
}];
block里面对需要进行约束的位置设置代码,例如make.top.left.mas_equalTo(0);它是设置logoView距离(如果没有设置视图默认为父视图)它的父视图的top、left距离为0,也就是居左、顶部,因为top、left他们距离父视图的距离相同就可以一次性的设置过去,当然上面的写法可以写成:
make.top.mas_equalTo(0);
make.left.mas_equalTo(0);
也可以写成
make.top.equalTo(self.view.mas_top);//self.view为父视图
make.left.equalTo(self.view.mas_left);
上面的写法也可以写成
make.top.equalTo(self.view.mas_top).offset(0);//self.view为父视图
make.left.equalTo(self.view.mas_left).offset(0);offset(0)//距离self.view的左边向右偏移0个单位
还可以写成用边距来设置,例如:
make.top.equalTo(self.view.mas_topMargin);
make.left.equalTo(self.view.mas_leftMargin);
这些设置都是可以的,主要是看你个人习惯。
equalTo后面带的可以是视图或者值(需要的像素),如果带的是值需要将值包装一下如:make.top.equalTo(@(0));。
mas_equalTo后面带的是数值(可以是动态的数值),也只能是值。
如果你需要设置的约束是动态的,比如你确定宽度,高度不确定,那你只需要设置宽度的约束,让高度自适应就可以。反之,你确定高度不确定宽度,也是一样的道理。
注意如果你需要设置的约束为动态距离,即根据不同的屏幕间距(或边距)不同,那你设置的值不能写死,需要为动态值,跟frame设置动态距离是一样的。可以考虑用比例值,附带个宏:#define SCR_W(x) [UIScreen mainScreen].bounds.size.width/375*x。
Masonry属性
MASConstraintMaker(己身视图)
@property (nonatomic, strong, readonly) MASConstraint *left;//左边
@property (nonatomic, strong, readonly) MASConstraint *top;//上方
@property (nonatomic, strong, readonly) MASConstraint *right;//右边
@property (nonatomic, strong, readonly) MASConstraint *bottom;//下方
@property (nonatomic, strong, readonly) MASConstraint *leading;//头部
@property (nonatomic, strong, readonly) MASConstraint *trailing;//尾部
@property (nonatomic, strong, readonly) MASConstraint *width;//宽度
@property (nonatomic, strong, readonly) MASConstraint *height;//高度
@property (nonatomic, strong, readonly) MASConstraint *centerX;//横向居中,即中心点X轴坐标
@property (nonatomic, strong, readonly) MASConstraint *centerY;//纵向居中,即中心点Y轴坐标
@property (nonatomic, strong, readonly) MASConstraint *baseline;//文本基线
在iOS 8之后可以设置它的间距、边距约束
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;//左边边距
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;//右边边距
@property (nonatomic, strong, readonly) MASConstraint *topMargin;//上方边距
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;//下方边距
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;//头部边距
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;//尾部边距
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;//X轴中心点边距
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;//Y轴中心点边距
上面好像没说说道size属性,这个也是一样的既然有了width、hight,那当然也有size。
MASViewAttribute(对比视图)
与本身视图是一一对应的,只是需要添加上mas_,例如:mas_bottom
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);
iOS 8之后
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;
本身视图属性、跟对比视图属性在苹果原生的约束布局上其实是一样的,只是masonry做了区别。
常用布局
一些常用的布局,例如:等分、优先级、自动布局、更新等。
1、等分布局
// 水平
[@[self.imageButton,
self.linkButton,
self.fontButton,
self.sizeButton,
self.alignmentButton,
self.colorButton,
self.keyboardButton] mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:pading leadSpacing:pading tailSpacing:pading];
// 高度
[@[self.imageButton,
self.linkButton,
self.fontButton,
self.sizeButton,
self.alignmentButton,
self.colorButton,
self.keyboardButton] mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self).offset(-kEditorToolBarButtonBottomSpace);
make.height.equalTo(@(kEditorToolBarButtonWidth));
}];
均等布局mas_distributeViewsAlongAxis
设置完水平布局或者垂直布局后,需要设置每一个控件自身的布局属性。
2、优先级约束
priceLabel
显示的优先级比originalPriceLabel
高,即先满足priceLabel
。
[self addSubview:self.priceLabel];
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.titleLabel.mas_bottom).mas_equalTo(4);
make.left.equalTo(self.ebookImageView.mas_right).mas_equalTo(15);
make.height.mas_equalTo(24);
}];
[self addSubview:self.originalPriceLabel];
[self.originalPriceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.priceLabel);
make.left.equalTo(self.priceLabel.mas_right).mas_equalTo(10);
make.right.mas_lessThanOrEqualTo(self.mas_right).with.offset(-5);
}];
[self.originalPriceLabel setContentCompressionResistancePriority:(UILayoutPriorityDefaultLow) forAxis:(UILayoutConstraintAxisHorizontal)];
[self.priceLabel setContentCompressionResistancePriority:(UILayoutPriorityDefaultHigh) forAxis:(UILayoutConstraintAxisHorizontal)];
setContentCompressionResistancePriority
需要与mas_lessThanOrEqualTo
配套使用才能起到相应的效果。
3、tableView动态布局UITableViewAutomaticDimension
iOS 8 之后可以使用的动态约束UITableViewAutomaticDimension,动态布局主要还是在cell 上控件的约束,使用方法:
_messageTableView.rowHeight = UITableViewAutomaticDimension;
[_messageTableView setEstimatedRowHeight:44];
或者
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
自定义Cell的时候,将约束写在- (void)layoutSubviews可能会存在一个问题那就是约束未被初始化导致第一次加载tableView的时候高度不准确,在拖动或者滚动后动态高度才是正确的,看下面效果图:
造成这个的原因是:将约束写在 - (void)layoutSubviews方法时需要另外调用
[self layoutIfNeeded];
来更新约束,但是写在
- (void)layoutSubviews
容易导致内存的增加,因为每次调用
[self layoutIfNeeded];
会增加
mas_makeConstraints
增加约束导致内存消耗,不是很建议该写法:
[_titleLabel setText:@"这是系统消息"];
[_contentLabel setText:@"摸摸摸爱妃玛科技发福了卡积分老卡机啊福利肯德基奥拉夫控件的啊发送的发送啊发送的发送发送的发送方公司的反感"];
[_timeLabel setText:@"2017-09-08 10:10"];
// 约束如果是写在layoutSubviews别漏了这句代码,调用约束。
// [self layoutIfNeeded];
建议写法:
将cell 的约束写在只加载一次的方法中例如- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
。
4、更新约束
很多时候还是需要根据数据来对布局进行处理,从而需要对约束进行更新。
原本的约束代码:
[_fullView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_couponLabel.mas_bottom).mas_offset(SCR_W * 10);
make.left.equalTo(_iconView.mas_right).offset(SCR_W * 10);
make.size.mas_equalTo(CGSizeMake(SCR_W * 25, SCR_W * 15));
}];
在需要更新约束的地方,调用如下代码:
//更新控件头部的位置
[_fullView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_dashedView.mas_bottom).mas_offset(SCR_W * 10);
}];
// 如果约束写在- (void)layoutSubviews, 需要立即更新请调用以下方法
// [self layoutIfNeeded];
注意:更新之前跟更新之后的约束是要针对同个对象才能生效,例如需要更新make.left.equalTo(self.view.mas_left).mas_offset(10)
,更新后为make.left.equalTo(self.view.mas_left).mas_offset(50)
,而不能是make.left.equalTo(self.view.mas_right).mas_offset(10)
。
Masonry使用优化
缘由
使用Masonry已经有一段时间了,但是发现项目中tableView做了复用但是每次滚动,内存都会一直增加,对就是增加根本停不下来。开始怀疑人生了,毕竟复用就那些注意点,但是检查了十几遍的代码还是没发现问题。直到重复滚了好好几十遍的tableView后确认了一个问题:在更新cell上面的某个控件的时候会造成一个问题就是卡顿、内存增加。有了目标开始针对性查找,最后确认了一个事 —— 官方文档对updateConstraints的解释:
Custom views that set up constraints themselves should do so by overriding this method.When your custom view notes that a change has been made to the view that invalidates one of its constraints, it should immediately remove that constraint, and then call setNeedsUpdateConstraints to note that constraints need to be updated. Before layout is performed, your implementation of updateConstraints will be invoked, allowing you to verify that all necessary constraints for your content are in place at a time when your custom view’s properties are not changing.
重点是说在对没用的或失效的约束应该立即删除。而Masonry的mas_makeConstraints方法是添加约束。每添加一层,内存增长一次,你不删除,它就一直在。
解决
在需要重新增加整个控件的约束时使用mas_remakeConstraints
而不是再次使用mas_makeConstraints
,因为mas_remakeConstraints
会先移除原本的约束,再重新添加。
对于只需要更新部分约束布局时则使用mas_updateConstraints
。
强调
mas_makeConstraints
只会新增约束,不管原本是否已经有约束。
mas_remakeConstraints
会先移除约束,再新增约束。
再尝试滚动tableView的性能得以优化,不会再增加内存。
技巧
1、子视图相对于父视图居中,可以考虑设置它的X、Y轴约束
2、子视图填满父视图,可以考虑设置子视图的四个边距为0
3、动态高度,可以只设置该视图的宽,高度由约束自动生成(动态高度,然后需要设置最大高度,好像是没法设置的)
就简单说这几点吧,具体的还是靠实践。多撸代码,也就熟能生巧了。
4、动画或者改变位置,可以将需要更改的某条约束定义为全局,然后根据需求进行更改。例如: _leftConstraint
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
_leftConstraint = make.left.mas_equalTo(SCR_W(15));
make.top.equalTo(@15);
make.size.mas_equalTo(CGSizeMake(SCR_W(345), SCR_W(15)));
}];
结语
总的介绍就这些吧(后续会更新),具体的还是需要看你自己多去尝试、实践才能孰能生巧。代码上可以简便一些,通用的布局,熟了可能一句约束就OK不熟的话可能需要拆分写好几句,才能达到相应的效果。