页面布局知识梳理

在iOS中布局UI经常使用的几种方式

  1. 经过设置视图的frame
CGRectMake(<#CGFloat x#>, <#CGFloat y#>, <#CGFloat width#>, <#CGFloat height#>)
复制代码

设置view.frame会当即生效,但明显这种方式会将视图固定死,若是要在不一样尺寸的屏幕上都显示完美比较难,可能须要些几套UI或者设置比例。算法

  1. 使用Autoresizing来适配父视图bounds发生改变的状况 数组

  2. 使用AutoLayout自动布局技术 苹果自iOS 6开始引入AutoLayout自动布局技术,通过一系列的优化,开发效率大大提升,苹果官方也推荐使用AutoLayout自动布局技术来布局UI界面。使用AutoLayout布局时,能够指定一系列的视图约束,如:高度、宽度。整个界面上的全部约束组合在一块儿就明确的定义了整个界面的布局。bash

自动布局AutoLayout

功能实现

  1. 利用NSLayoutConstraint类给视图建立具体的约束对象:
NSLayoutConstraint *layoutConstraint = [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeRight multiplier:1 constant:20.f];
复制代码

公式:函数

view1.attribute = view2.attribute * multiplier + constant;
复制代码
  1. 添加约束到相应的视图上:

API:布局

- (void)addConstraint:(NSLayoutConstraint *)constraint;
- (void)addConstraints:(NSArray *)constraints;
复制代码

约束添加规则:性能

  • 对于两个相同层级上的视图之间的约束关系(约束对象),添加到它们的父视图上。
  • 对于两个不一样层级上的视图之间的约束关系,添加到它们最近的共同父视图上。
  • 对于两个有层级关系的视图之间的约束,添加到层级较高的父视图上。
  1. 注意事项
  • 使用autolayout就要禁止autoresizing功能:view.translatesAutoresizingMaskIntoConstraints = NO;
  • 由上面的约束添加规则可知:添加约束以前,必需要肯定视图之间的层级关系,所以必定要保证相关控件都已经被添加到了各自的父视图上。
  • 不要重复对视图布局,即不须要再设置frame等。

实现原理

iOS中想要肯定一个视图的布局只须要两个属性:origin/center和size。也就是

  • x & y(相对父视图)
  • width & height

只要直到了这些数据就能够固定一个视图了。 使用view.frame就是最直接的方式,而Auto Layout中大部分的约束都是描述性语言,表示视图间的相对距离。如上图:优化

A.left = superView.left + 20;
    A.top = superView.top + 25;
    A.width = 100;
    A.height = 100;
    
    B.left = A.left + 20;
    B.top = A.top;
    B.width = A.width;
    B.height = A.height;
复制代码

咱们经过求解👆的八元一次方程组,是能够将A、B两个视图布局所须要的信息(x、y、width、height)求解出来的。实质上由AutoLayout引擎在运行时求解上述的方程组,最终使用frame来绘制视图。所以也能够知道AutoLayout不能当即生效的(systemLayoutSizeFitting能够获取自动布局结果),不一样于直接设置frame。动画

Auto Layout 其实就是对 Cassowary(将布局问题抽象成线性等式和不等式约束进行求解) 算法的一种实现。ui

性能

使用AutoLayout布局,就是让整个界面上的全部约束(线性等式/不等式)在一块儿明确的、无冲突定义整个界面的布局。(当发生冲突时,AutoLayout会先尝试打破一些优先级低的约束,尽可能知足优先级高的约束)spa

使用AutoLayout的过程是须要先求解方程组的,获得结果再设置frame。这样的时间复杂度就远高于直接设置frame了。当视图层级很深或者对性能要求较高的页面使用自动布局的性能就差强人意了。

第三方封装 Masonry

实现
  1. 使用AutoLayout布局代码:
NSLayoutConstraint *layoutConstraint1 = [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeRight multiplier:1 constant:20.f];
NSLayoutConstraint *layoutConstraint2 = [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeTop multiplier:1 constant:0];
[view1 addConstraints:@[layoutConstraint1,layoutConstraint2]];
复制代码

NSLayoutConstraint 布局约束对象

NSLayoutAttribute 布局属性对象(左右上下等)

NSLayoutRelation 布局关系对象(>、<、=等待)

  1. 使用Masonry编写相同约束:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
         make.left.equalTo(view2.mas_right).offset(20);
         make.top.equalTo(view2.mas_top);
 }];
复制代码

能够看出两种方式是类似,Masonry的语言最后能够被转化成AutoLayout代码。

设计到一些关键词:

  • makeConstraints:
  • make:
  • left\right\top...
  • equalTo
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    // 1. 要使用AutoLayout,须要关闭autoSizing
    self.translatesAutoresizingMaskIntoConstraints = NO;
    // 2. 建立ConstraintMaker对象(它负责建立相关约束)
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    // 3. 执行block,即建立各类约束:    
    //   constraintMaker.left.equalTo(view2.mas_right).offset(20);
    //   constraintMaker.top.equalTo(view2.mas_top);
    block(constraintMaker);
    return [constraintMaker install];// 添加约束
}
复制代码
MASConstraintMaker
// step 1
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// step 3
// MASViewAttribute对一个视图和它的布局属性的封装
// MASViewConstraint 一条 约束(view.left和view2.right)封装了两个MASViewAttributes:firstViewAttribute和secondViewAttribute,和以后的约束关系、offset等数值
// 返回约束对象MASViewConstraint

- (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]) {
        // constaint 存在(left),newConstraint(right)
        //采用了链式约束(make.left.right)就用组合约束(left.right)替换前面的旧约束(left),即用left和right的组合约束替换left约束
        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;
}
复制代码

返回的MASViewConstraint对象继承自MASConstraint,这个约束对象仍然有left、top等布局属性。子类继承父类属性,而且它重写了addConstraintWithLayoutAttribute:方法。

// 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");
    // 它的代理就是MASConstraintMaker对象
    // 可是constraint不是nil而是当前的约束如make.left.top,当前约束是left,进入方法建立的是top约束,重回step3
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

// 在约束数组中找到旧约束,并用新的组合约束替换
- (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];
}

复制代码

所以Masonry可使用链式调用,如:make.left.top;且布局属性返回的都是MASConstraint对象,它还有布局关系属性equalTo(view2.right)等。 经过添加布局关系属性和后边的offset等,将约束对象MASViewConstraint的约束条件补充完整了。

这样一条条完整的约束就建立完成了。接下来须要将这些完整的约束添加到view上。即又回到了mas_makeConstaints方法上了,它最终执行[constraintMaker install]

- (NSArray *)install {
    if (self.removeExisting) {//remake 删除原有已经设置的全部约束
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    // self.constaints是以前方法中出现的约束数组:存储关于这个视图的全部约束语句,让每一个约束都执行install方法
    NSArray *constraints = self.constraints.copy;
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;// 是不是要updateConstraints
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}
复制代码

[constraint install]方法取出该约束对象中的全部约束条件,建立了约束对象MASLayoutConstraint,它继承自系统的NSLayoutConstaint:

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

那么这个约束要被添加到哪一个view上呢?

// 查找要被添加约束的installView
    // 1. 设置secondView,则按照添加约束规则找出共同父视图
    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) { // 2. 未设置secondView,可是当前布局属性是视图width、height。
        self.installedView = self.firstViewAttribute.view;
    } else { // 3. 以上都不是,则secondView应该默认是当前视图的父视图了
        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];
    }
复制代码

equalTo()和mas_equalTo()

#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
复制代码

equalTo()参数是id类型,而mas_equalTo()参数能够是id、数值、CGSize等等,它就是一个宏,经过MASBoxValue函数将其中参数转化成id类型。

left和mas_left

  1. left是maker和MASConstraint的属性;而mas_left是UIView(MASAdditions)分类中给view添加的属性。所以mas_left能够用于equalTo(参数)参数。
  2. 两个属性最终都会被包装成MASViewAttribute布局属性。
总结

综上能够看出: MASConstraintMaker是一个简单工厂类(或者说工厂方法)用来生产MASConstraint约束对象。

外界经过给工厂传递不一样的类型left、right等,从而生产出不一样类型的约束对象(MASViewConstraint和MASCompositeConstraint),利用多态。

动画

相关文章
相关标签/搜索