iOS Masonry

Masonry简介

  • Masonry利用简化,链式和富有表现力的语法,利用AutoLayout NSLayoutConstraints的强大功能。 支持iOS和OSX自动布局。我们一般很少直接用苹果的原生API进行布局和适配,市场上使用很多的就是OC使用Masonry,如果是swift语言,那么就使用SnapKit。他们是同一个作者,感兴趣的可以点击这里Masonry/SnapKit.

Masonry主要的类和常用api

  • View+MASAdditions,是UIView(iOS)/NSView(OSX)的一个分类,给UIView的添加一些属性和方法,其MASViewAttribute类型属性与MASConstraintMakerMASConstraint属性一一对应,只不过前面多了一个前缀mas_,常见的api有:
//寻找两个视图的最近的公共父视图(类比两个数字的最小公倍数)
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
//创建添加安装约束
- (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;
  • MASViewAttribute类是对UIViewNSLayoutAttribute的封装,使用等式来表示就是MASViewAttribute = UIView + NSLayoutAttribute + item。在MASViewAttribute类中的view属性表示所约束的对象,而item就是该对象上可以被约束的部分。一般不在外部使用.

  • MASConstraintMaker类负责创建 MASConstraint类型的对象(依赖于MASConstraint接口,而不依赖于具体实现)。在UIViewView+MASAdditions类目中就是调用的MASConstraintMaker类中的一些方法。我们在使用MasonrysubView添加约束时,mas_makeConstraints方法中的Block的参数就是MASConstraintMaker的对象,用户可以通过该Block回调过来的MASConstraintMaker对象给View指定要添加的约束以及该约束的值。该工厂中的constraints属性数组就记录了该工厂创建的所有MASConstraint对象

    约束属性:

    @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轴中心点边距
    
  • MASConstraint作为约束,是MASViewConstraintMASCompositeConstraint的父类,其中MASViewConstraint指单个约束,而MASCompositeConstraint指复合约束,链式语法就与MASCompositeConstraint相关,下面会细讲。

#import "MASUtilities.h"
// 允许使用可链接语法创建约束。 约束可以表示单个NSLayoutConstraint(MASViewConstraint)
// 或一组NSLayoutConstraints(MASComposisteConstraint)
@interface MASConstraint : NSObject

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, 
// NSLayoutAttributeRight之一的MASConstraints
- (MASConstraint * (^)(MASEdgeInsets insets))insets;

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, 
// NSLayoutAttributeRight之一的MASConstraints
- (MASConstraint * (^)(CGFloat inset))inset;

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeWidth, NSLayoutAttributeHeight, 
// 之一的MASConstraints
- (MASConstraint * (^)(CGSize offset))sizeOffset;

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeCenterX, NSLayoutAttributeCenterY, 
// 之一的MASConstraints
- (MASConstraint * (^)(CGPoint offset))centerOffset;

// 修改NSLayoutConstraint常数
- (MASConstraint * (^)(CGFloat offset))offset;

// 基于值类型,修改NSLayoutConstraint常数
- (MASConstraint * (^)(NSValue *value))valueOffset;

//设置NSLayoutConstraint的multiplier属性
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;

//设置NSLayoutConstraint的multiplier为1.0/dividedBy
- (MASConstraint * (^)(CGFloat divider))dividedBy;

//设置NSLayoutConstraint的优先级为一个浮点数或者MASLayoutPriority
- (MASConstraint * (^)(MASLayoutPriority priority))priority;

// 设置NSLayoutConstraint的优先级为MASLayoutPriorityLow
- (MASConstraint * (^)(void))priorityLow;

// 设置NSLayoutConstraint优先级为MASLayoutPriorityMedium
- (MASConstraint * (^)(void))priorityMedium;

//设置NSLayoutConstraint优先级为MASLayoutPriorityHigh
- (MASConstraint * (^)(void))priorityHigh;

//设置约束关系为NSLayoutRelationEqual,返回一个block,
//接收一下类型参数MASViewAttribute, UIView, NSValue, NSArray
- (MASConstraint * (^)(id attr))equalTo;

//设置约束关系为NSLayoutRelationGreaterThanOrEqual,返回一个block,
//接收一下类型参数MASViewAttribute, UIView, NSValue, NSArray
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;

//设置约束关系为NSLayoutRelationLessThanOrEqual,返回一个block,
//接收一下类型参数MASViewAttribute, UIView, NSValue, NSArray
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;


// 可选的语义属性,它不起作用,但提高了约束的可读性
- (MASConstraint *)with;

// 可选的语义属性,它不起作用,但提高了约束的可读性
- (MASConstraint *)and;

// 根据调用的属性创建一个新的MASCompositeConstraint
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)

- (MASConstraint *)firstBaseline;
- (MASConstraint *)lastBaseline;

#endif

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)

- (MASConstraint *)leftMargin;
- (MASConstraint *)rightMargin;
- (MASConstraint *)topMargin;
- (MASConstraint *)bottomMargin;
- (MASConstraint *)leadingMargin;
- (MASConstraint *)trailingMargin;
- (MASConstraint *)centerXWithinMargins;
- (MASConstraint *)centerYWithinMargins;

#endif


//设置约束constraint的debug name
- (MASConstraint * (^)(id key))key;

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, 
// NSLayoutAttributeRight之一的MASConstraints
- (void)setInsets:(MASEdgeInsets)insets;

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, 
// NSLayoutAttributeRight之一的MASConstraints
- (void)setInset:(CGFloat)inset;

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeWidth, NSLayoutAttributeHeigh之一的MASConstraints
- (void)setSizeOffset:(CGSize)sizeOffset;

// 修改NSLayoutConstraint常数,仅仅影响第一个item的NSLayoutAttribute
// 是下面NSLayoutAttributeCenterX, NSLayoutAttributeCenterY之一的MASConstraints
- (void)setCenterOffset:(CGPoint)centerOffset;

//修改NSLayoutConstraint常量
- (void)setOffset:(CGFloat)offset;

#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)

// 是否在修改约束时通过动画代理
@property (nonatomic, copy, readonly) MASConstraint *animator;
#endif

// 如果OS支持就激活一个NSLayoutConstraint,否则就调用install
- (void)activate;

// 销毁前面安装或者激活的NSLayoutConstraint
- (void)deactivate;

//创建一个NSLayoutConstraint并将它添加到合适的view上
- (void)install;

//移除以前安装的NSLayoutConstraint
- (void)uninstall;

@end


//用于MASConstraint方法的便捷自动装箱宏

#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

@interface MASConstraint (AutoboxingSupport)

//等于
- (MASConstraint * (^)(id attr))mas_equalTo;
//大于等于
- (MASConstraint * (^)(id attr))mas_greaterThanOrEqualTo;
//小于等于
- (MASConstraint * (^)(id attr))mas_lessThanOrEqualTo;

//设置一个常量
- (MASConstraint * (^)(id offset))mas_offset;

@end

Masonry基本使用

1.frame 形式x,y,w,h都固定,父子视图约束

 [redView mas_makeConstraints:^(MASConstraintMaker *make) {
     
     //方式1:
//        make.left.mas_equalTo(10);
//        make.top.mas_equalTo(64);
//        make.width.mas_equalTo(100);
//        make.height.mas_equalTo(100);
     //方式2:
//        make.left.equalTo(@10);
//        make.top.equalTo(@64);
//        make.width.equalTo(@100);
//        make.height.equalTo(@100);
     //方式3:
     make.left.equalTo(self.view).with.offset(10);
     make.top.equalTo(self.view).with.offset(64);
     make.width.equalTo(@100);
     make.height.equalTo(@100);
 }];

2.多个子视图之间约束,红绿蓝三个子视图等高且距父视图的左右下边距个为10,顶部间距为88

 CGFloat padding = 10;
 [redView mas_makeConstraints:^(MASConstraintMaker *make) {
     //因为每个属性的get方法都返回值都为MASConstraint,从而达到链式语法的目的
     make.left.right.top.equalTo(self.view).insets(UIEdgeInsetsMake(88, padding, 0, padding));
     make.bottom.equalTo(self->greenView.mas_top).offset(-padding);
 }];
 
 [greenView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, 0, padding));
     make.bottom.equalTo(self->blueView.mas_top).offset(-padding);
 }];
 /**
  下面设置make.height的数组是关键,通过这个数组可以设置这三个视图高度相等。其他例如宽度之类的,也是类似的方式。
  */
 [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
      make.left.right.bottom.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, padding, padding));
     make.height.equalTo(@[self->redView,self->greenView]);
 }];
iOS Masonry_第1张图片
多个子视图之间约束.png

3.多个子视图之间自由布局

[redView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.equalTo(@10);
     make.top.equalTo(@108);
     make.width.equalTo(@100);
     make.height.equalTo(@100);
 }];
 
 [greenView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.equalTo(self->redView.mas_right).offset(10);
//        make.top.equalTo(self->redView);
     //方式2
     make.top.equalTo(self->redView.mas_top);
     make.width.equalTo(@200);
     make.height.equalTo(@200);
 }];
 
 [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.equalTo(self->redView).offset(0);
     //        make.top.equalTo(self->redView);
     //方式2
     make.top.equalTo(self->greenView.mas_bottom).offset(10);
     make.width.equalTo(self.view).offset(-20);
     make.height.equalTo(@300);
 }];
iOS Masonry_第2张图片
多个子视图之间自由布局.png

4.居中

  [redView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.center.equalTo(self.view);
       make.width.equalTo(@300);
       make.height.equalTo(@300);
   }];
   
   [greenView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.center.equalTo(self.view);
       make.width.equalTo(@200);
       make.height.equalTo(@200);
   }];
   
   [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.center.equalTo(self.view);
       make.width.equalTo(@100);
       make.height.equalTo(@100);
   }];
   
   blueView.layer.cornerRadius = 50;
   blueView.clipsToBounds = YES;
iOS Masonry_第3张图片
居中.png

5.垂直居中,及垂直方向上的中心点位置一致

    CGFloat gap = 10;
    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.view);
        make.left.mas_equalTo(gap);
        make.right.mas_equalTo(self->greenView.mas_left).offset(-gap);
        make.height.equalTo(@100);
    }];
    
    [greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.view.mas_centerY);
        make.right.mas_equalTo(self->blueView.mas_left).offset(-gap);
        make.height.equalTo(@200);
        make.width.equalTo(self->redView);
    }];
    
    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.view);
        make.right.mas_equalTo(self.view.mas_right).offset(-gap);
        make.height.equalTo(@300);
        make.width.equalTo(self->redView);
    }];
    
iOS Masonry_第4张图片
垂直居中.png

6.水平居中.水平直方向上的中心点位置一致,高度相等,宽度分别为100,200,300

    CGFloat gap = 10;
    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view);
        make.top.mas_equalTo(64);
        make.width.equalTo(@100);
        make.height.equalTo(@[self->greenView,self->blueView]);
    }];
    
    [greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view.mas_centerX);
        make.top.mas_equalTo(self->redView.mas_bottom).offset(gap);
        make.width.equalTo(@200);
//        make.width.equalTo(self->redView);
    }];
    
    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view);
        make.top.mas_equalTo(self->greenView.mas_bottom).offset(gap);
        make.width.equalTo(@300);
        make.bottom.equalTo(self.view).offset(-gap);
    }];
iOS Masonry_第5张图片
水平居中.png

7.更新约束,只会更新同类型的约束,有时候我们需要移除一个视图,并且别的视图又与该视图有约束关系,这时候就搞更新约束

   //如果其他视图与blueView有约束关系,直接移除会产生问题
    [blueView removeFromSuperview];
    //所以要更新约束或重新设置约束,或者多设置约束,设置不同优先级,当高优先级的约束不存在时就会启用低优先级的约束
    [greenView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(self.view).offset(-10);
    }];
iOS Masonry_第6张图片
更新约束.png

8.重置约束,会移除之间所有的约束,所以不能只设置某一个或某几个约束,保证约束齐全,只是当前视图的约束被清除重置了,但是别的视图相对于该视图的约束并没有被清除

[redView mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(@10);
        make.top.equalTo(@100);
        make.width.equalTo(@200);
        make.height.equalTo(@200);
    }];
iOS Masonry_第7张图片
重置约束.png
  1. UITableView动态Cell高度,iOS8之后,UITableView动态Cell高度在不考虑性能的情况下,tableView动态Cell高度,可以采取估算高度的方式。如果通过估算高度的方式实现的话,无论是纯代码还是Interface Builder,都只需要两行代码就可以完成Cell自动高度适配。只需要设置estimatedRowHeight估算高度,设置rowHeightUITableViewAutomaticDimension高度自动计算.

    用Masonry自定义cell,关键点在于顶部视图距离contentView的顶部间距,底部视图距离contentView的底部间距,子视图之间的约束都必须不能缺少.

//Masonry布局自定义cell
#import 
#import "HJLabel.h"
NS_ASSUME_NONNULL_BEGIN

@interface HJTableViewCell : UITableViewCell
@property (nonatomic,strong) UILabel *titleL;
@property (nonatomic,strong) UIImageView *imageV;
@property (nonatomic,strong) HJLabel *contentL;
@property (nonatomic,strong) UILabel *pjL;
@end
NS_ASSUME_NONNULL_END

//-----实现文件---
#import "HJTableViewCell.h"
#import "Masonry.h"
@implementation HJTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        self.selectionStyle = UITableViewCellSelectionStyleNone;
        [self initviews];
        //布局
        [self setup];
    }
    return self;
}

- (void)initviews {
    self.titleL = [self initsLabel];
    self.imageV = [[UIImageView alloc]init];
    self.imageV.backgroundColor = UIColor.purpleColor;
    self.imageV.contentMode = UIViewContentModeScaleAspectFill;
    self.imageV.image = [UIImage imageNamed:@"girl"];
    self.imageV.backgroundColor = UIColor.purpleColor;
    self.contentL = [self initshjLabel];
    self.pjL = [self initsLabel];
    [self.contentView addSubview:self.titleL];
    [self.contentView addSubview:self.imageV];
    [self.contentView addSubview:self.contentL];
    [self.contentView addSubview:self.pjL];
    
}

- (UILabel *)initsLabel {
    UILabel * label = [[UILabel alloc]init];
    label.backgroundColor = UIColor.redColor;
    label.numberOfLines = 0;
    label.font = [UIFont systemFontOfSize:14];
    return label;
}

- (HJLabel *)initshjLabel {
    HJLabel * label = [[HJLabel alloc]init];
    label.verticalAlignment = VerticalAlignmentTop;
    label.backgroundColor = UIColor.redColor;
    label.numberOfLines = 0;
    label.font = [UIFont systemFontOfSize:14];
    return label;
}
//布局子控件
- (void)setup {
    CGFloat gap = 10;
    [self.titleL mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(gap);
        make.top.mas_equalTo(gap);
        //小于等于
        make.right.mas_lessThanOrEqualTo(self.imageV.mas_left).offset(-gap);
        //大于等于
        make.height.mas_greaterThanOrEqualTo(20);
    }];
    
    [self.imageV mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.mas_equalTo(-gap);
        make.top.mas_equalTo(gap);
        make.width.mas_equalTo(100);
        make.height.mas_equalTo(100);
    }];
    [self.contentL mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(gap);
        make.top.mas_equalTo(self.titleL.mas_bottom).offset(10);
        //小于等于默认间距 8
        make.right.mas_lessThanOrEqualTo(self.imageV.mas_left);
        //高度最小值80
        make.height.mas_greaterThanOrEqualTo(70);
    }];
    [self.pjL mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(gap);
        make.top.mas_equalTo(self.contentL.mas_bottom).offset(10);
        make.right.mas_lessThanOrEqualTo(self.contentView).offset(-2*gap);
        make.height.mas_greaterThanOrEqualTo(10);
        //正常布局不需要在设置bottom,因为高度和top都已经设置了,这里这么设置就是为了计算cell的高度
        make.bottom.equalTo(self.contentView).offset(-10);
    }];
}

tableView相关设置

- (void)setup {
    self.tableView.estimatedRowHeight = 140;
    //ios8y以后设置自动计算高度
    self.tableView.rowHeight = UITableViewAutomaticDimension;
    [self.tableView registerClass:[HJTableViewCell class] forCellReuseIdentifier:@"HJTableViewCell"];
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    HJTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"HJTableViewCell" forIndexPath:indexPath];
    cell.titleL.text = @"text";
    if (indexPath.row % 2 == 0) {
        cell.contentL.text = @"上海车团网络有限公司";
    }else{
        cell.contentL.text = @"上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司上海车团网络有限公司";
    }

    cell.pjL.text = @"666";
    return cell;
}
iOS Masonry_第8张图片
UITableView动态高度cell.png

注意 : 有时候我们在tableview第一次加载时候会出现显示不正确的情况,可以再viewDidAppear方法中刷新tableview来解决

- (void)viewDidAppear:(BOOL)animated {
   [super viewDidAppear:animated];
   [self.tableView reloadData];
}
  1. UIScrollView自动布局,其实跟上面cell是一样的,当内部视图的X,Y,W,H都确定了,然后就可以通过对scrollView的上下左右约束一下就可得出其contentSize大小,有两种方式,一种是直接添加到UIScrollView上,另一种是先给UIScrollView添加一个containerView作为contentView,再将视图都添加到containerView上.

    10.1.方式1:视图直接添加到UIScrollView

     - (void)setup {
    [self.scrollV addSubview:self.redView];
    [self.scrollV addSubview:self.blueView];
    [self.scrollV addSubview:self.greenView];
    [self.scrollV addSubview:self.label];
    
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(click)];
    [self.scrollV addGestureRecognizer:tap];
    
    [self.scrollV mas_makeConstraints:^(MASConstraintMaker *make) {
        //设置内边距
        make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(88, 10, 33, 10));
    }];
    CGFloat gap = 10;
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.equalTo(self.scrollV).offset(gap);
        make.width.height.mas_equalTo(300);
    }];
    
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.scrollV).offset(gap);
        make.top.equalTo(self.redView.mas_bottom).offset(gap);
        make.width.height.equalTo(self.redView);
    }];
    
    [self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.scrollV).offset(gap);
        make.top.equalTo(self.blueView.mas_bottom).offset(gap);
        make.width.height.equalTo(self.redView);
    }];
    
    [self.label mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.scrollV).offset(gap);
        make.top.equalTo(self.greenView.mas_bottom).offset(gap);
        //设置最大宽度
        make.width.lessThanOrEqualTo(self.redView);
        //设置宽度
        //make.width.equalTo(self.redView);
        //设置最小高度,不设置label高度.则label高度默认为0,根据文字多少和自身宽度来自动计算自身高度
        //make.height.greaterThanOrEqualTo(@10);
        //下面这句约束很重要,一般情况下不用设置,约束条件也满足自动布局,但是这里是UIScrollView,多设置该约束是为了计算出去contentSize,从而达到UIScrollView的宽高自适应.
        make.right.bottom.equalTo(self.scrollV).offset(-gap);
    }];
    }
    
    - (void)click {
    NSString *str = @"你大爷还是你大爷!";
    static NSInteger i = 1;
    NSMutableString * resultStr = [NSMutableString string];
    for (NSInteger j = 0; j
    UIScrollView自动布局方式1.gif

    10.2.方式1:先给UIScrollView添加一个containerView作为contentView,再将视图都添加到containerView上.

- (void)setup2 {
[self.scrollV addSubview:self.containerView];
[self.containerView addSubview:self.redView];
[self.containerView addSubview:self.blueView];
[self.containerView addSubview:self.greenView];
[self.containerView addSubview:self.label];

[self.scrollV mas_makeConstraints:^(MASConstraintMaker *make) {
   //设置内边距
   make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(88, 10, 33, 10));
}];

[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.edges.equalTo(self.scrollV);
}];
CGFloat gap = 10;
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.top.left.equalTo(self.containerView).offset(gap);
   make.bottom.equalTo(self.blueView.mas_top).offset(-gap);
   make.width.equalTo(@300);
   make.height.equalTo(@300);
}];
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.left.equalTo(self.containerView).offset(gap);
   make.bottom.equalTo(self.greenView.mas_top).offset(-gap);
   make.size.equalTo(self.redView);
}];
[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
   make.left.equalTo(self.containerView).offset(gap);
   make.size.equalTo(self.redView);
   make.bottom.equalTo(self.label.mas_top).offset(-gap);
}];

[self.label mas_makeConstraints:^(MASConstraintMaker *make) {
   make.left.equalTo(self.containerView).offset(gap);
   make.width.equalTo(self.redView);
   make.height.greaterThanOrEqualTo(self.redView);
    //这个就是为了约束self.containerView的宽高,而上面又添加了containerView距scrollV的内边距,从而就可以计算出contentSize,从而达到UIScrollView的宽高自适应.
   make.bottom.right.equalTo(self.containerView).offset(-gap);
}];
}
UIScrollView自动布局方式2.gif
由上面可以看出不管是方式1还是方式2,实现UIScrollView自动布局最关键的代码为` make.right.bottom.equalTo(self.scrollV).offset(-gap);`,`make.bottom.right.equalTo(self.containerView).offset(-gap);`,自内部子视图的约束已经完全满足的情况下,多设置该约束是为了计算出去contentSize,从而达到UIScrollView的宽高自适应.

Masonry结构与源码简单解析

  • Masonry框架结构如下:

    iOS Masonry_第9张图片
    Masonry框架结构.png

    详细解析请参考:iOS开发之Masonry框架源码解析,这里主要讲解一下如何实现链式语法来添加约束的.

  • 流程分析:
    示例:

    
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.equalTo(self.scrollV).offset(gap);
        make.width.height.mas_equalTo(300);
    }];
    
    

    mas_makeConstraintsView+MASAdditions分类给UIView增加的添加约束的方法,内部实现为:

    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //设置为自动布局
    self.translatesAutoresizingMaskIntoConstraints = NO;
    //初始化MASConstraintMaker对象
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    //block回调传值
    block(constraintMaker);
    //安装约束
    return [constraintMaker install];
     }
    

    MASConstraintMaker的构造方法:

    - (id)initWithView:(MAS_VIEW *)view {
    self = [super init];
    if (!self) return nil;
    //持有当前视图
    self.view = view;
    //用于保存block传进来的约束
    self.constraints = NSMutableArray.new;
    
    return self;
    }
    

    block回调传值,示例中blcok实现时会调用make.left.top.equalTo(self.scrollV).offset(gap);方法

    //重写了left属性的get方法,返回的是MASConstraint类型
    - (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
    }
    //添加约束
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
    }
      
    #pragma mark - MASConstraintDelegate
    - (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];
     }
    
    - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    //constraint是上一次添加的`MASConstraint`实例,是新添加的layoutAttribute约束要包装成MASViewConstraint类型实例
    
    //MASViewAttribute绑定view和约束
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
     //转为MASViewConstraint类型
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    //如果是MASViewConstraint就是复合型约束,如make.left.top.equalTo(self.scrollV).offset(gap);的top时,即调用MASConstraint的top方法时走这里,
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        //创建一个复合类型的约束
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        //并交换位置插入到constraints中
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        //返回的是MASCompositeConstraint类型其实是MASConstraint的子类
        return compositeConstraint;
    }
    //单个约束,如make.left.top.equalTo(self.scrollV).offset(gap);的left时就会走这里
    if (!constraint) {
    //设置代理
        newConstraint.delegate = self;
        //添加到约束数组中
        [self.constraints addObject:newConstraint];
    }
    //返回的MASViewConstraint类型也是MASConstraint的子类
    return newConstraint;
    }
    
    

    可以看出当调用make.left的时候很好理解,就是重写make的left属性的getter方法,创建添加一个MASViewConstraint约束到make的constraints数组中,并设置其代理为当前MASConstraintMaker实例,并返回一个该是类型的实例变量,而MASViewConstraint的实例变量是持有当前视图和当前添加的约束NSLayoutAttributeLeft.

    当调用make.left.top的时候,是调用MASConstraint的top的getter方法:

    
    //也是重写了getter方法
    - (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
    }
    //但是这里并没有实现
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
    }
    

    这里并没有在MASConstraint类中实现addConstraintWithLayoutAttribute方法,但是在,而是在子类中实现的,MASConstraint的子类有MASViewConstraintMASCompositeConstraint,MASViewConstraint中该方法实现如下:

    #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];
    }
    

    可以看出最终由代理去实现addConstraintWithLayoutAttribute方法,而前面说过其代理就是当前MASConstraintMaker实例,然后又是创建一个创建添加一个MASViewConstraint约束到make的constraints数组中,并设置其代理为当前MASConstraintMaker实例,并返回一个该是类型的实例变量,而MASViewConstraint的实例变量是持有当前视图和当前添加的约束NSLayoutAttributeTop.这样每次都返回一个新的MASViewConstraint或者MASCompositeConstraint,这样就可以完成链式表达式赋值,MASCompositeConstraint的构造方法如下:

     - (id)initWithChildren:(NSArray *)children {
    self = [super init];
    if (!self) return nil;
    
    _childConstraints = [children mutableCopy];
    for (MASConstraint *constraint in _childConstraints) {
        constraint.delegate = self;
    }
    
     return self;
     }
    

    MASCompositeConstraint并没有什么实际意义,只不过吃用_childConstraints数组包含的是MASViewConstraint,然后替换掉MASConstraintMakerconstraints之前添加的元素.

`MASConstraintMaker`的`install`方法是为了安装约束
```objectivec
- (NSArray *)install {
//如果标记了已经存在的约束,就先移除
if (self.removeExisting) {
    NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
    for (MASConstraint *constraint in installedConstraints) {
        [constraint uninstall];
    }
}
NSArray *constraints = self.constraints.copy;
//变了约束MASConstraint类型
for (MASConstraint *constraint in constraints) {
    constraint.updateExisting = self.updateExisting;
    //安装
    [constraint install];
}
[self.constraints removeAllObjects];
return constraints;
 } 
```

可以看出真正的安装操作是调用`MASConstraint`的`install`方法,但是`MASConstraint`中并没有实现该方法,而最终都是在其子类`MASViewConstraint`中实现:
```objectivec
- (void)install {
if (self.hasBeenInstalled) {
    return;
}

if ([self supportsActiveProperty] && self.layoutConstraint) {
    self.layoutConstraint.active = YES;
    [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
    return;
}

MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
    secondLayoutItem = self.firstViewAttribute.view.superview;
    secondLayoutAttribute = firstLayoutAttribute;
}

MASLayoutConstraint *layoutConstraint
    = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                    attribute:firstLayoutAttribute
                                    relatedBy:self.layoutRelation
                                       toItem:secondLayoutItem
                                    attribute:secondLayoutAttribute
                                   multiplier:self.layoutMultiplier
                                     constant:self.layoutConstant];

layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;

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;
}


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];
}
}
```
  • 综上可得:

    1. View+MASAdditions类为UIView扩展了添加,更新,重置约束的方法,和相关的间距和宽高属性.

    2. MASConstraintMaker相当于真正的主持,添加约束保存到组中,并且传发送下载消息

    3. MASViewAttribute持有视图和约束,及对视图和约束的包装.

    4. MASConstraint其仅仅做了包装传递MASViewAttribute的功能和完成链式语法,并且其方法实现是在子类实现的,而其子类MASViewConstraint则真正完成约束的安装和卸载.

Masonry使用技巧与注意事项

  • 视图必须初始化并添加到父视图上以后,才能使用Masonry布局.
  • mas_makeConstraints只会新增约束,不管原本是否已经有约束。mas_remakeConstraints会先移除约束,再新增约束。有时候我们动态的添加约束时可能导致内存暴增,这时候可以考虑使用mas_remakeConstraints.
  • equalTo可传入值的类型有:MASViewAttribute, UIView, NSValue,NSNumber, NSArray(前面几种类型的数组);
  • mas_equalTo 和上面功能相同,参数可以传递基本数据类型和对象,可以理解为比上面的API更强大
  • 用下面两种宏定义声明一下,就不需要区分mas_前缀。
    // 定义这个常量,就可以不用在开发过程中使用"mas_"前缀。
    #define MAS_SHORTHAND
    // 定义这个常量,就可以让Masonry帮我们自动把基础数据类型的数据,自动装箱为对象类型。
    #define MAS_SHORTHAND_GLOBALS
    
  • 修饰语句:Masonry为了让代码使用和阅读更容易理解,所以直接通过点语法就可以调用,还添加了andwith两个方法。这两个方法内部实际上什么都没干,只是在内部将self直接返回,功能就是为了更加方便阅读,对代码执行没有实际作用。
  • 子视图相对于父视图居中,可以考虑设置它的X、Y轴约束
  • 子视图填满父视图,可以考虑设置子视图的四个边距为0
  • 动画或者改变位置,可以将需要更改的某条约束定义为全局,然后根据需求进行更改。
  • UILabel,UIImage,UIButton这可以根据内容而自适应大小的我们可以不约束其宽高
  • cell自适应高度时,一定要在子视图约束满足后,设置其contentView的上下边距并且设置tableView的cell估算高度,cell的高度设置为自适应.
  • lessThanOrEqualTo小于等于,greaterThanOrEqualTo大于等于经常会用在UILabel,UIImage,UIButton这种可以根据能容来计算size的视图中,可以用来占用一定的空间,而达到自动计算尺寸的效果.如可以设UILabel的最大宽度为100,最小高度我10,这样改label的在没有文字时也会占有宽100高10的尺寸,而且会随着文字的增加而高度自动计算增加.

先关参考:

  • iOS自动布局框架 - Masonry详解

  • Masonry讲解与实战中内存优化

  • Masonry 框架详细解析

  • iOS开发之Masonry框架源码解析

  • 深入剖析Auto Layout,分析iOS各版本新增特性

  • 自动布局指南

  • NSLayout Anchor

  • 读 SnapKit 和 Masonry 自动布局框架源码

你可能感兴趣的:(iOS Masonry)