Autolayout原理及Masonry实现

Autolayout

介绍

自动布局区别于手动布局frame的一种布局技术,主要是为元素添加一些关于位置的相对约束关系,而非直接设置frame的位置,相对位置最终会被布局引擎换算成绝对位置,这样更能够适配不同屏幕和系统

使用

  • storyboard
  • 代码实现 AutoLayout 步骤
    1.利用 NSLayoutConstraint 类创建具体的约束对象;
    2.添加约束对象到相应的 view 上
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"Autolayout布局";
    UIView *blueView = [[UIView alloc] init];
    blueView.backgroundColor = [UIColor blueColor];
    blueView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:blueView];
    // 添加width约束
    NSLayoutConstraint *blueWidthConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
    [blueView addConstraint:blueWidthConstraint];
    // 添加height约束
    NSLayoutConstraint *blueHeightConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:150];
    [blueView addConstraint:blueHeightConstraint];
    // 添加left约束
    NSLayoutConstraint *blueLeftConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];
    [self.view addConstraint:blueLeftConstraint];
    // 添加top约束
    NSLayoutConstraint *blueTopConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];
    [self.view addConstraint:blueTopConstraint];
    
    /*UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    redView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:redView];
    
    NSLayoutConstraint *redWidthConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
    [redView addConstraint:redWidthConstraint];
    
    NSLayoutConstraint *redHeightConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:150];
    [redView addConstraint:redHeightConstraint];
    
    NSLayoutConstraint *redLeftConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];
    [self.view addConstraint:redLeftConstraint];
    
    NSLayoutConstraint *redTopConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:100];
    [self.view addConstraint:redTopConstraint];
}*/
 NSLayoutConstraint *redLeftConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];

[NSLayoutConstraint constraintWithItem:view1 attribute:attr1 relatedBy:relation toItem:view2 attribute:attr2 multiplier:1.0 constant:c];

view1: 要约束的控件(redView)
☞ attr1: 约束的属性(常量),(这里是NSLayoutAttributeLeft)
☞ relation: 与参照控件之间的关系(常量),包括等于、大于等于、小于等于
☞ view2: 参照的控件(self.view)
☞ attr2: 约束的属性(常量)
☞ multiplier: 倍数(1.0)
☞ c: 常量,做好了上述的约束之后会加上这个常量(50)

所以我们得出 AutoLayout 的核心计算公式:

obj1.property1 =(obj2.property2 * multiplier)+ constant value

原理

约束-》布局-》渲染
公式: view1.attr1 = view2.attr2 * multiplier + constant

当为View增加Constraint时,这些约束会组成一个多元一次方程组,View所在的Window会调用Auto Layout 引擎计算得到解集equation,其中,求出的解集会应用到 UIView渲染过程中,当做其 frame属性中的值来使用

Masonry

用于自动布局的第三方库,使用方便,代码简洁、优雅

用法

[redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view).offset(50);
        make.top.equalTo(blueView.mas_bottom).offset(100);        
        make.width.mas_equalTo(100);
        make.height.mas_equalTo(150);        
}];

1.mas_makeConstraints

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //默认情况下它是YES,即view的autoresizing mask会自动成为它的布局。如果我们希望手动布局,需要将它设为NO
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

MASConstraintMaker负责为视图创建各个属性的约束,[constraintMaker install] 为视图添加约束
2.make(MASConstraintMaker)

make.left.equalTo(self.view).offset(50);
// step 1
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

// step 3
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

MASViewAttribute是约束属性类,存储着view、视图属性等关系
MASViewConstraint 这是一个约束,它包含firstViewAttribute和secondViewAttribute。firstViewAttribute为当前视图的约束属性,secondViewAttribute是要参照视图的属性

// step 4
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
    } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
        _secondViewAttribute = secondViewAttribute;
    } else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
    }
}

equalTo将传入的参数赋值给MASViewConstraint的secondViewAttribute
make.left.equalTo(self.view).offset(50)这句代码为当前视图的某一个属性创建一个与参照视图的约束关系
3.链式调用

make.left.top.equalTo(self);

make.left的返回类型是MASViewConstraint,那MASViewConstraint中是如何调用到top的呢?
从MASViewConstraint的父类MASConstraint可以看到,这里也定义了所有的布局属性,但实际是将添加约束这个操作委托给了代理方法,如下,例:

// MASConstraint.m
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

// MASViewConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

创建MASViewConstraint的时候,将delegate设置为MASConstraintMaker,因此实际是调用了之前step3的方法,但与之前不同的是,第一个参数不是nil,而是self,即make.left返回的约束对象

// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// step 3
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;

为视图创建了多个属性的组合约束MASCompositeConstraint,equalTo则为组合约束中的每一个单独的约束对象添加参照视图约束关系
4.[constraintMaker install]
[constraintMaker install]其实没做什么,,主要的工作是[constraint install]
创建一个约束对象,然后添加到最近的公共superview上

5.updateConstraint和remakeConstraint
MASConstraintMaker中有两个属性updateExisting和removeExisting,install的时候根据这两个属性,如果是更新约束,直接更新constant值更新约束;如果是remake则将当前视图install过的约束uninstall之后再重新install一遍

- (NSArray *)install {
    if (self.removeExisting) {
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    NSArray *constraints = self.constraints.copy;
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}

性能

在使用 Auto Layout 进行布局时,可以指定一系列的约束,比如视图的高度、宽度等等。而每一个约束其实都是一个简单的线性等式或不等式,整个界面上的所有约束在一起就明确地(没有冲突)定义了整个系统的布局。
因为布局系统在最后仍然需要通过 frame 来进行,所以 Auto Layout 虽然为开发者在描述布局时带来了一些好处,不过它相比原有的布局系统加入了从约束计算 frame 的过程,

参考文章

AutoLayout&Masonry&约束的底层实现原理
寒哥细谈之 AutoLayout 全解
史上比较用心的纯代码实现 AutoLayout
从 Auto Layout 的布局算法谈性能
Masonry实现原理并没有那么可怕
WWDC2018观看笔记一:Auto Layout 底层原理

你可能感兴趣的:(Autolayout原理及Masonry实现)