Masonry 构建状态 覆盖率状态 Carthage 兼容 Pod 版本

Masonry 仍在积极维护中,我们致力于修复错误并合并来自更广泛社区的优质 PR。但是,如果您在项目中使用 Swift,我们建议使用 SnapKit,因为它提供了更好的类型安全性和更简单的 API。

Masonry 是一个轻量级的布局框架,它用更友好的语法封装了 AutoLayout。 Masonry 有自己的布局 DSL,它提供了一种可链式的方式来描述您的 NSLayoutConstraints,从而使布局代码更加简洁和易读。 Masonry 支持 iOS 和 Mac OS X。

有关示例,请查看 Masonry 工作区中的 Masonry iOS Examples 项目。下载后需要运行 pod install

NSLayoutConstraints 有什么问题?

在底层,Auto Layout 是一种强大而灵活的方式来组织和布局你的视图。然而,从代码创建约束是冗长的,而且不是很具有描述性。想象一个简单的例子,你想让一个视图填充它的父视图,但在每一边都插入 10 个像素

UIView *superview = self.view;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

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

[superview addConstraints:@[

    //view1 constraints
    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeTop
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeTop
                                multiplier:1.0
                                  constant:padding.top],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeLeft
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeLeft
                                multiplier:1.0
                                  constant:padding.left],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeBottom
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeBottom
                                multiplier:1.0
                                  constant:-padding.bottom],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeRight
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeRight
                                multiplier:1
                                  constant:-padding.right],

 ]];

即使对于这样一个简单的例子,所需的代码也很冗长,并且当你有超过 2 或 3 个视图时,很快就会变得难以阅读。另一种选择是使用 Visual Format Language (VFL),它稍微简洁一些。然而,ASCII 类型语法有它自己的陷阱,而且也很难进行动画,因为 NSLayoutConstraint constraintsWithVisualFormat: 返回一个数组。

准备好迎接你的制造者吧!

这是使用 MASConstraintMaker 创建的相同约束

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

另请注意,在第一个示例中,我们必须将约束添加到父视图 [superview addConstraints:...。然而,Masonry 会自动将约束添加到适当的视图。

Masonry 也会为你调用 view1.translatesAutoresizingMaskIntoConstraints = NO;

并非所有事物都是平等的

.equalTo 相当于 NSLayoutRelationEqual

.lessThanOrEqualTo 相当于 NSLayoutRelationLessThanOrEqual

.greaterThanOrEqualTo 相当于 NSLayoutRelationGreaterThanOrEqual

这三个相等约束接受一个参数,它可以是以下任何一种

1. MASViewAttribute

make.centerX.lessThanOrEqualTo(view2.mas_left);
MASViewAttribute NSLayoutAttribute
view.mas_left NSLayoutAttributeLeft
view.mas_right NSLayoutAttributeRight
view.mas_top NSLayoutAttributeTop
view.mas_bottom NSLayoutAttributeBottom
view.mas_leading NSLayoutAttributeLeading
view.mas_trailing NSLayoutAttributeTrailing
view.mas_width NSLayoutAttributeWidth
view.mas_height NSLayoutAttributeHeight
view.mas_centerX NSLayoutAttributeCenterX
view.mas_centerY NSLayoutAttributeCenterY
view.mas_baseline NSLayoutAttributeBaseline

2. UIView/NSView

如果你想让 view.left 大于或等于 label.left

//these two constraints are exactly the same
make.left.greaterThanOrEqualTo(label);
make.left.greaterThanOrEqualTo(label.mas_left);

3. NSNumber

Auto Layout 允许将宽度和高度设置为常量值。如果你想设置视图具有最小和最大宽度,你可以将数字传递给等式块

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

然而,Auto Layout 不允许将对齐属性(如 left、right、centerY 等)设置为常量值。因此,如果你为这些属性传递一个 NSNumber,Masonry 会将它们转换为相对于视图父视图的约束,即

//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));

默认情况下,支持 自动装箱 的宏以 mas_ 为前缀。 通过在导入 Masonry 之前定义 MAS_SHORTHAND_GLOBALS,可以使用不带前缀的版本。

4. NSArray

包含前面任何类型的混合数组

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

学会确定优先级

.priority 允许你指定一个确切的优先级

.priorityHigh 相当于 UILayoutPriorityDefaultHigh

.priorityMedium 是高和低之间的一半

.priorityLow 相当于 UILayoutPriorityDefaultLow

优先级可以像这样添加到约束链的末尾

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

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

组合,组合,组合

Masonry 还为你提供了一些方便的方法,可以同时创建多个约束。 这些被称为 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);

坚持住,亲爱的

有时你需要修改现有约束以进行动画或删除/替换约束。 在 Masonry 中,有几种不同的方法来更新约束。

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_remakeConstraints 的用武之地。

mas_remakeConstraints 类似于 mas_updateConstraints,但它不是更新常量值,而是在再次安装之前删除其所有约束。 这使你可以提供不同的约束,而不必保留想要删除的约束的引用。

- (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);
        }
    }];
}

你可以在 Masonry iOS Examples 项目中找到所有三种方法的更详细示例。

当 ^&*!@ 击中风扇时!

布局你的视图并不总是按计划进行。 因此,当事情变得非常糟糕时,你不想看到像这样的控制台输出

Unable to simultaneously satisfy constraints.....blah blah blah....
(
    "<NSLayoutConstraint:0x7189ac0 V:[UILabel:0x7186980(>=5000)]>",
    "<NSAutoresizingMaskLayoutConstraint:0x839ea20 h=--& v=--& V:[MASExampleDebuggingView:0x7186560(416)]>",
    "<NSLayoutConstraint:0x7189c70 UILabel:0x7186980.bottom == MASExampleDebuggingView:0x7186560.bottom - 10>",
    "<NSLayoutConstraint:0x7189560 V:|-(1)-[UILabel:0x7186980]   (Names: '|':MASExampleDebuggingView:0x7186560 )>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7189ac0 V:[UILabel:0x7186980(>=5000)]>

Masonry 向 NSLayoutConstraint 添加了一个类别,该类别覆盖了 - (NSString *)description 的默认实现。 现在你可以为视图和约束提供有意义的名称,并且还可以轻松地挑出 Masonry 创建的约束。

这意味着你的控制台输出现在可以看起来像这样

Unable to simultaneously satisfy constraints......blah blah blah....
(
    "<NSAutoresizingMaskLayoutConstraint:0x8887740 MASExampleDebuggingView:superview.height == 416>",
    "<MASLayoutConstraint:ConstantConstraint UILabel:messageLabel.height >= 5000>",
    "<MASLayoutConstraint:BottomConstraint UILabel:messageLabel.bottom == MASExampleDebuggingView:superview.bottom - 10>",
    "<MASLayoutConstraint:ConflictingConstraint[0] UILabel:messageLabel.top == MASExampleDebuggingView:superview.top + 1>"
)

Will attempt to recover by breaking constraint
<MASLayoutConstraint:ConstantConstraint UILabel:messageLabel.height >= 5000>

有关如何设置此功能的示例,请查看 Masonry 工作区中的 Masonry iOS Examples 项目。

我应该在哪里创建我的约束?

@implementation DIYCustomView

- (instancetype)init {
    if (self = [super init]) {
        // --- 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

安装

使用 orsome CocoaPods

在你的 Podfile 中

pod 'Masonry'

如果你想使用 masonry 而没有那些令人讨厌的 'mas_' 前缀。 在导入 Masonry 之前,将 #define MAS_SHORTHAND 添加到你的 prefix.pch

#define MAS_SHORTHAND

开始 Masoning

#import "Masonry.h"

代码片段

将包含的代码片段复制到 ~/Library/Developer/Xcode/UserData/CodeSnippets 以闪电般的速度编写你的 masonry 块!

mas_make -> [<#view#> mas_makeConstraints:^(MASConstraintMaker *make) { <#code#> }];

mas_update -> [<#view#> mas_updateConstraints:^(MASConstraintMaker *make) { <#code#> }];

mas_remake -> [<#view#> mas_remakeConstraints:^(MASConstraintMaker *make) { <#code#> }];

特点

待办事项