Masonry

Masonry

Masonry的一些用法

在使用Masonry时,如果不想使用mas_前缀,可在.pch文件中,在导入Masonry.h之前,定义MAS_SHORTHAND

//define this constant if you want to use Masonry without the 'mas_' prefix
//如果使用Masonry时,不带有`mas_`前缀,定义该常量
#define MAS_SHORTHAND

//define this constant if you want to enable auto-boxing for default syntax
//如果你想启用auto-boxing,则定义该常量
#define MAS_SHORTHAND_GLOBALS

#import "Masonry.h"

By default, macros which support autoboxing are prefixed with mas_. Unprefixed versions are available by defining MAS_SHORTHAND_GLOBALS before importing Masonry.
默认情况下, mas_开头的是自动支持autoboxing,对于非mas_开头的,在导入Masonry之前,要定义MAS_SHORTHAND_GLOBALS

在源码中mas_equalTo定义如下,MASBoxValue相当与是自动装箱,所以支持原始值

#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))

使用方式

官方文档介绍

Prepare to meet your Maker!

添加基本约束

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

上面的代码还可以继续简化

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

Not all things are created equal

Masonry会自动调用view1.translatesAutoresizingMaskIntoConstraints = NO;

除了使用.equalTo,还可以使用.lessThanOrEqualTo.greaterThanOrEqualTo

如果想要 view.left 大于或者等于 label.left

//下面2个约束是一样的
make.left.greaterThanOrEqualTo(label);
make.left.greaterThanOrEqualTo(label.mas_left);

Auto Layout允许width和height设置为常量值。如果你想设置view有一个最小宽度和最大宽度:

//width >= 200 && width <= 400
make.width.greaterThanOrEqualTo(@200);
make.width.lessThanOrEqualTo(@400)

然而Auto Layout并不允许对齐属性attributes,例如left、right、centerY等设置为常量。如果个这些值传递常量,Masonry会自动转为相对于superview的约束:

//creates view.left = view.superview.left + 10
make.left.lessThanOrEqualTo(@10)

除了使用NSNumber,还可以使用原始值和结构体,例如:

make.top.mas_equalTo(42);
make.height.mas_equalTo(20);
make.size.mas_equalTo(CGSizeMake(50, 100));
make.edges.mas_equalTo(UIEdgeInsetsMake(10, 0, 10, 0));
make.left.mas_equalTo(view).mas_offset(UIEdgeInsetsMake(10, 0, 10, 0));

也可以使用NSArray

make.height.equalTo(@[view1.mas_height, view2.mas_height]);
make.height.equalTo(@[view1, view2]);
make.left.equalTo(@[view1, @100, view3.right]);

Learn to prioritize

.priority allows you to specify an exact priority 指定一个确定的优先级

.priorityHigh equivalent to UILayoutPriorityDefaultHigh

.priorityMedium is half way between high and low

.priorityLow equivalent to UILayoutPriorityDefaultLow

优先级可放在约束链的尾部

make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow();

make.top.equalTo(label.mas_top).with.priority(600);

Composition, composition, composition

有一些便利方法可在同一时间创建多个约束,称为MASCompositeConstraints

edges

// make top, left, bottom, right equal view2
make.edges.equalTo(view2);

// make top = superview.top + 5, left = superview.left + 10,
//      bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))

size

// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)

// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))

center

// make centerX and centerY = button1
make.center.equalTo(button1)

// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))

可以将属性链起来增加可读性:

// All edges but the top should equal those of the superview
make.left.right.and.bottom.equalTo(superview);
make.top.equalTo(otherView);

Hold on for dear life

1.持有引用

持有一个特定约束的引用,作为一个本地的变量或者类的属性

// in public/private interface
@property (nonatomic, strong) MASConstraint *topConstraint;

...

// when making constraints
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];

...
// then later you can call
[self.topConstraint uninstall];

2.mas_updateConstraints

如果你只是更新约束的常量值,可以使用mas_updateConstraints代替mas_makeConstraints

// this is Apple's recommended place for adding/updating constraints
// this method can get called multiple times in response to setNeedsUpdateConstraints
// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints
- (void)updateConstraints {
    [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self);
        make.width.equalTo(@(self.buttonSize.width)).priorityLow();
        make.height.equalTo(@(self.buttonSize.height)).priorityLow();
        make.width.lessThanOrEqualTo(self);
        make.height.lessThanOrEqualTo(self);
    }];

    //according to apple super should be called at end of method
    [super updateConstraints];
}

3.mas_remakeConstraints

mas_updateConstraints用来更新一系列约束时非常有用,

mas_remakeConstraintsmas_updateConstraints类似,它在install约束之前,会移除所有的约束

- (void)changeButtonPosition {
    [self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.size.equalTo(self.buttonSize);

        if (topLeft) {
        	make.top.and.left.offset(10);
        } else {
        	make.bottom.and.right.offset(-10);
        }
    }];
}

Where should I create my constraints?

在哪儿创建约束呢?

@implementation DIYCustomView

- (id)init {
    self = [super init];
    if (!self) return nil;

    // --- Create your views here ---
    self.button = [[UIButton alloc] init];

    return self;
}

// tell UIKit that you are using AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
    return YES;
}

// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {

    // --- remake/update constraints here
    [self.button remakeConstraints:^(MASConstraintMaker *make) {
        make.width.equalTo(@(self.buttonSize.width));
        make.height.equalTo(@(self.buttonSize.height));
    }];
    
    //according to apple super should be called at end of method
    [super updateConstraints];
}

- (void)didTapButton:(UIButton *)button {
    // --- Do your changes ie change variables that affect your layout etc ---
    self.buttonSize = CGSize(200, 200);

    // tell constraints they need updating
    [self setNeedsUpdateConstraints];
}

@end

Example中的例子

基本使用

    //if you want to use Masonry without the mas_ prefix
    //define MAS_SHORTHAND before importing Masonry.h see Masonry iOS Examples-Prefix.pch
    [greenView makeConstraints:^(MASConstraintMaker *make) {
        make.top.greaterThanOrEqualTo(superview.top).offset(padding);
        make.left.equalTo(superview.left).offset(padding);
        make.bottom.equalTo(blueView.top).offset(-padding);
        make.right.equalTo(redView.left).offset(-padding);
        make.width.equalTo(redView.width);

        make.height.equalTo(redView.height);
        make.height.equalTo(blueView.height);
        
    }];

    //with is semantic and option
    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(superview.mas_top).with.offset(padding); //with with
        make.left.equalTo(greenView.mas_right).offset(padding); //without with
        make.bottom.equalTo(blueView.mas_top).offset(-padding);
        make.right.equalTo(superview.mas_right).offset(-padding);
        make.width.equalTo(greenView.mas_width);
        
        make.height.equalTo(@[greenView, blueView]); //can pass array of views
    }];
    
    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(greenView.mas_bottom).offset(padding);
        make.left.equalTo(superview.mas_left).offset(padding);
        make.bottom.equalTo(superview.mas_bottom).offset(-padding);
        make.right.equalTo(superview.mas_right).offset(-padding);
        make.height.equalTo(@[greenView.mas_height, redView.mas_height]); //can pass array of attributes
    }];

Masonry_第1张图片

使用常量

    [purpleView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(@20);
        make.left.equalTo(@20);
        make.bottom.equalTo(@-20);
        make.right.equalTo(@-20);
    }];
    
    // auto-boxing macros allow you to simply use scalars and structs, they will be wrapped automatically
    
    [orangeView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(CGPointMake(0, 50)); //中心Y偏移Y50
        make.size.equalTo(CGSizeMake(200, 100)); //设置size
    }];

Masonry_第2张图片

使用edge

    UIView *lastView = self;
    for (int i = 0; i < 10; i++) {
        UIView *view = UIView.new;
        view.backgroundColor = [self randomColor];
        view.layer.borderColor = UIColor.blackColor.CGColor;
        view.layer.borderWidth = 2;
        [self addSubview:view];
        
        [view mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(lastView).insets(UIEdgeInsetsMake(5, 10, 15, 20));
        }];
        
        lastView = view;
    }

Masonry_第3张图片

Aspect Fit

类似于图片的Aspect Fit模式

        // Inner views are configured for aspect fit with ratio of 3:1
        [self.topView addSubview:self.topInnerView];
        [self.topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.width.equalTo(self.topInnerView.mas_height).multipliedBy(3); //宽度为高度的3倍
            
            make.width.and.height.lessThanOrEqualTo(self.topView);//width height小于或等于父view
            make.width.and.height.equalTo(self.topView).with.priorityLow();//width height等于父view 低优先级
            
            make.center.equalTo(self.topView); //居中
        }];
        
        [self.bottomView addSubview:self.bottomInnerView];
        [self.bottomInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.height.equalTo(self.bottomInnerView.mas_width).multipliedBy(3); //高度为宽度的3倍
            
            make.width.and.height.lessThanOrEqualTo(self.bottomView);
            make.width.and.height.equalTo(self.bottomView).with.priorityLow();
                        
            make.center.equalTo(self.bottomView);
        }];

Masonry_第4张图片

UILabel

如果是多行label,需要设置lablel的preferredMaxLayoutWidth
例子中是在view的layoutSubviews方法中,设置这个值,但是在模拟器上运行时,有bug,官方demo如下:

- (void)layoutSubviews {
    [super layoutSubviews];

    // for multiline UILabel's you need set the preferredMaxLayoutWidth
    // you need to do this after [super layoutSubviews] as the frames will have a value from Auto Layout at this point

    // stay tuned for new easier way todo this coming soon to Masonry

    CGFloat width = CGRectGetMinX(self.shortLabel.frame) - kPadding.left;
    width -= CGRectGetMinX(self.longLabel.frame);
    self.longLabel.preferredMaxLayoutWidth = width;

    // need to layoutSubviews again as frames need to recalculated with preferredLayoutWidth
    [super layoutSubviews];
}

对于一行有多个label时,可能还需要设置ContentHuggingPriorityContentCompressionResistance的优先级

[label1 setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];

可参考:

  • Masonry的使用
  • 多个label,可变长情况下使用Masonry

ScrollView

先给scrollView添加约束,在scrollView上添加一个contentView,在contentView上添加子view,相当于将scrollview撑起来

- (id)init {
    self = [super init];
    if (!self) return nil;
    
    UIScrollView *scrollView = UIScrollView.new;
    self.scrollView = scrollView;
    scrollView.backgroundColor = [UIColor grayColor];
    [self addSubview:scrollView];
    [self.scrollView makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self);
    }];
    
    [self generateContent];

    return self;
}

- (void)generateContent {
    UIView* contentView = UIView.new;
    [self.scrollView addSubview:contentView];
    
    [contentView makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.scrollView);
        make.width.equalTo(self.scrollView);
    }];
    
    UIView *lastView;
    CGFloat height = 25;
    
    for (int i = 0; i < 10; i++) {
        UIView *view = UIView.new;
        view.backgroundColor = [self randomColor];
        [contentView addSubview:view];
        
        UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
        [view addGestureRecognizer:singleTap];
        
        [view mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(lastView ? lastView.bottom : @0);
            make.left.equalTo(@0);
            make.width.equalTo(contentView.width);
            make.height.equalTo(@(height));
        }];
        
        height += 25;
        lastView = view;
    }
    
    [contentView makeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(lastView.bottom);
    }];
}

Masonry_第5张图片

Array

可以同时对多个view的数组,同时进行更新约束

- (void)updateConstraints {
    [self.buttonViews updateConstraints:^(MASConstraintMaker *make) {
        make.baseline.equalTo(self.mas_centerY).with.offset(self.offset);
    }];
    
    //according to apple super should be called at end of method
    [super updateConstraints];
}

AttributeChain

    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(greenView.mas_bottom).insets(padding);

        // chain attributes
        make.left.right.and.bottom.equalTo(superview).insets(padding);

        make.height.equalTo(@[greenView, redView]);
    }];

DistributeView

即可以均分多个view

- (id)init {
    self = [super init];
    if (!self) return nil;

    NSMutableArray *arr = @[].mutableCopy;
    for (int i = 0; i < 4; i++) {
        UIView *view = UIView.new;
        view.backgroundColor = [self randomColor];
        view.layer.borderColor = UIColor.blackColor.CGColor;
        view.layer.borderWidth = 2;
        [self addSubview:view];
        [arr addObject:view];
    }
    
    unsigned int type  = arc4random()%4;
    //type = 3;
    switch (type) {
        case 0:
            [arr mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:20 leadSpacing:5 tailSpacing:5];
            [arr makeConstraints:^(MASConstraintMaker *make) {
                make.top.equalTo(@60);
                make.height.equalTo(@60);
            }];
            break;
        case 1:
            [arr mas_distributeViewsAlongAxis:MASAxisTypeVertical withFixedSpacing:20 leadSpacing:5 tailSpacing:5];
            [arr makeConstraints:^(MASConstraintMaker *make) {
                make.left.equalTo(@0);
                make.width.equalTo(@60);
            }];
            break;
        case 2:
            [arr mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:30 leadSpacing:200 tailSpacing:30];
            [arr makeConstraints:^(MASConstraintMaker *make) {
                make.top.equalTo(@60);
                make.height.equalTo(@60);
            }];
            break;
        case 3:
            [arr mas_distributeViewsAlongAxis:MASAxisTypeVertical withFixedItemLength:30 leadSpacing:30 tailSpacing:200];
            [arr makeConstraints:^(MASConstraintMaker *make) {
                make.left.equalTo(@0);
                make.width.equalTo(@60);
            }];
            break;
            
        default:
            break;
    }
    
    return self;
}

Masonry_第6张图片Masonry_第7张图片
Masonry_第8张图片Masonry_第9张图片

更新约束

有关于修改约束,Apple文档介绍如下:Changing Constraints

Update Pass是:

The system traverses the view hierarchy and calls the updateViewConstraints method on all view controllers, and the updateConstraints method on all views. You can override these methods to optimize changes to your constraints

系统遍历view层级,在控制器中调用updateViewConstraints方法,在view上调用 updateConstraints方法

所以在Masonry中,也是重写了view的updateConstraints方法(注意在方法最后要调用父类的方法),调用setNeedsUpdateConstraints 方法

要注意,不要在updateConstraints方法中调用setNeedsUpdateConstraints方法

其demo如下(有修改):

// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {

    [self.growingButton updateConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self);
        [make.width.equalTo(@(self.buttonSize.width)) priorityLow];
        [make.height.equalTo(@(self.buttonSize.height)) priorityLow];
        [make.width.lessThanOrEqualTo(self) priorityHigh];
        [make.height.lessThanOrEqualTo(self) priorityHigh];
    }];
    
    //according to apple super should be called at end of method
    [super updateConstraints];
}

//按钮的点击事件
- (void)didTapGrowButton:(UIButton *)button {
    self.buttonSize = CGSizeMake(self.buttonSize.width * 1.3, self.buttonSize.height * 1.3);

    // tell constraints they need updating
    [self setNeedsUpdateConstraints];

    // update constraints now so we can animate the change
    [self updateConstraintsIfNeeded];

    [UIView animateWithDuration:0.4 animations:^{
        [self layoutIfNeeded];
    }];
}

还有就是,如果更新约束时要做动画,建议如下:

[containerView layoutIfNeeded];
[UIView animateWithDuration:1.0 animations:^{
  // Make all constraint changes here
  [containerView layoutIfNeeded];
}];

效果如下:

Masonry_第10张图片

重新设置约束

使用remakeConstraints方法,如下:

// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
    
    [self.movingButton remakeConstraints:^(MASConstraintMaker *make) {
        make.width.equalTo(@(100));
        make.height.equalTo(@(100));
        
        if (self.topLeft) {
            make.left.equalTo(self.left).with.offset(10);
            make.top.equalTo(self.top).with.offset(10);
        }
        else {
            make.bottom.equalTo(self.bottom).with.offset(-10);
            make.right.equalTo(self.right).with.offset(-10);
        }
    }];
    
    //according to apple super should be called at end of method
    [super updateConstraints];
}

- (void)toggleButtonPosition {
    self.topLeft = !self.topLeft;
    
    // tell constraints they need updating
    [self setNeedsUpdateConstraints];
    
    // update constraints now so we can animate the change
    [self updateConstraintsIfNeeded];
    
    [UIView animateWithDuration:0.4 animations:^{
        [self layoutIfNeeded];
    }];
}

效果如下:

Masonry_第11张图片

调试

在使用过程中,如果设置约束不正确,调试起来会很麻烦
在控制台通过查看提示的信息,来对照view,但如果view复杂,就不知道那个view对应的是哪个view

Masonry_第12张图片

Masonry也提供了调试功能,通过添加debug key来调试,如:

    //you can attach debug keys to views like so:
    greenView.mas_key = @"greenView";
    redView.mas_key = @"redView";
    blueView.mas_key = @"blueView";
    superview.mas_key = @"superview";

或者如下的形式:

    //OR you can attach keys automagically like so:
    MASAttachKeys(greenView, redView, blueView, superview);

加入key后,警告将会变成如下的形式:

Masonry_第13张图片
也可以为constraints添加约束:

    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        //you can also attach debug keys to constaints
        make.edges.equalTo(@1).key(@"ConflictingConstraint"); //composite constraint keys will be indexed
        make.height.greaterThanOrEqualTo(@5000).key(@"ConstantConstraint");

        make.top.equalTo(greenView.mas_bottom).offset(padding);
        make.left.equalTo(superview.mas_left).offset(padding);
        make.bottom.equalTo(superview.mas_bottom).offset(-padding).key(@"BottomConstraint");
        make.right.equalTo(superview.mas_right).offset(-padding);
        make.height.equalTo(greenView.mas_height);
        make.height.equalTo(redView.mas_height).key(@340954); //anything can be a key
    }];

计算cell的高度

可参考:

  • AutoLayout框架Masonry使用心得
  • iOS-使用Masnory实现UITableViewCell自适应高度
  • UITableViewCell使用Masonry进行自动计算行高的问题

你可能感兴趣的:(iOS,布局,iOS,开源项目学习)