iOS-AutoLayout布局学习

AutoLayout是苹果在iOS6.0以后推出的多屏幕适配的布局工具,然而实际上它不仅仅是一种布局工具,它还是一种全新的布局思想。

AutoLayout是通过约束来实现布局的,一个UIView对象一旦使用的AutoLayout,那么它的frame属性将一直为0。
在使用AutoLayout进行布局之前我们先要做一些准备工作:

  1. 设置translatesAutoresizingMaskIntoConstraints = NO。简单来说,Autoresizing和AutoLayout是两种不同的布局理念,但是默认情况是是可以转化的,这里我们需要指定使用AutoLayout进行布局,禁止Autoresizing。
  2. 如果当前需要设置视图的对象是UIViewController,则将约束写在- (void)updateViewConstraints方法中;如果是UIView则可以将约束写在- (void)updateConstraints方法中。

AutoLayout我们可以看成主要是对视图进行三种类型的约束:

  • 视图大小约束
  • 视图间相对位置约束
  • 视图对齐约束

而无论是哪种约束,其在底层的实现主要是依赖于NSLayoutConstraint这个类。

NSLayoutConstraint

NSLayoutConstraint这个类主要有两个类方法,用于给视图添加约束的。其中可以以VFS方式进行约束的描述,或者以常规方式描述视图间约束条件。
我们可以先来看一下这两个添加约束条件的方法。

+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format 
    options:(NSLayoutFormatOptions)opts 
    metrics:(NSDictionary<NSString *,id> *)metrics 
      views:(NSDictionary<NSString *,id> *)views

第一个添加约束的方法是使用VFSfotmat则为传入的表达式,options选项可以对描述的约束进行进一步的设置。

我们更常用的是使用以下这个方法对约束进行描述,该方法可以对单个视图进行设置,也可以设置两个视图间的约束关系。

+ (instancetype)constraintWithItem:(id)view1 
    attribute:(NSLayoutAttribute)attr1 
    relatedBy:(NSLayoutRelation)relation 
       toItem:(id)view2 
    attribute:(NSLayoutAttribute)attr2 
   multiplier:(CGFloat)multiplier 
     constant:(CGFloat)c

参数说明:

  • view1: 被约束对象
  • attribute: 被约束对象的属性(比如说是上下左右边距之类的)
  • relatedBy: 约束关系。主要是等于,大于等于,小于等于三种关系。
  • toItem: 约束源
  • attribute: 约束源的属性,同被约束源。这两个结合说明了两者之间某两个属性的关系。
  • multiplier: 约束系数。
  • constant: 约束常数。

在官方说明中是这样描述约束的计算的。

view1.attr1 = view2.attr2 * multiplier + constant

接下来我们将对三种约束种类做一个简单说明。

视图大小约束

视图大小的约束的话主要是值针对被约束对象的,可以看做是没有约束源的约束。我们可以通过把约束源设置为nil来实现约束。

[self.view1 addConstraint:
    [NSLayoutConstraint constraintWithItem:self.view1
                                 attribute:NSLayoutAttributeWidth
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:nil
                                 attribute:NSLayoutAttributeNotAnAttribute
                                multiplier:0.0f
                                  constant:200]];

[self.view1 addConstraint:
    [NSLayoutConstraint constraintWithItem:self.view1
                                 attribute:NSLayoutAttributeHeight
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:nil
                                 attribute:NSLayoutAttributeNotAnAttribute
                                multiplier:0.0f
                                  constant:200]];

这里我们把约束源设置为了nil,如果此时约束源的属性也不存在时。可以用NSLayoutAttributeNotAnAttribute来描述,表示没有属性。上面那个设置的意思则是说明指定view1的宽度和高度都等于200。
PS:最后别忘了把获取的NSLayoutConstraint约束实例添加到对应的视图中!!!!!!!,其中视图大小的约束应该只添加到视图自身

视图间相对位置约束

视图间相对位置包括了上下左右间距之类的位置关系。需要注意的是视图间相对位置的约束可能会有以下情况。

  • 两个视图拥有共同父视图,则他们之间位置约束应该添加到父视图中。
  • 如果两个视图关系为父子关系(一个为另一个的父视图),则他们之间的位置约束应该添加到父视图中。

    [self.view2 addConstraint:
        [NSLayoutConstraint constraintWithItem:self.view2
                                     attribute:NSLayoutAttributeWidth
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:nil
                                     attribute:NSLayoutAttributeNotAnAttribute
                                    multiplier:0.0f
                                      constant:100]];
    
    [self.view2 addConstraint:
        [NSLayoutConstraint constraintWithItem:self.view2
                                     attribute:NSLayoutAttributeHeight
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:nil
                                     attribute:NSLayoutAttributeNotAnAttribute
                                    multiplier:0.0f
                                      constant:100]];
    
    [self.view addConstraint:
        [NSLayoutConstraint constraintWithItem:self.view2
                                     attribute:NSLayoutAttributeTopMargin
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:self.view1
                                     attribute:NSLayoutAttributeBottom
                                    multiplier:1.0f
                                      constant:20]];
    

正如上面的代码所示,前两个是约束视图大小的约束则添加到其自身,而第三个约束是让view2位于view1下方距离20的位置,并且它们有共同的父视图,所以这个约束是添加到它们共同的父视图中的。

视图对齐约束

视图对齐约束的话包括中心X/Y的对齐,上下左右边界的对齐等等。其视图添加原则和上面是类似的。

[self.view3 addConstraint:
 [NSLayoutConstraint constraintWithItem:self.view3
                              attribute:NSLayoutAttributeWidth
                              relatedBy:NSLayoutRelationEqual
                                 toItem:nil
                              attribute:NSLayoutAttributeNotAnAttribute
                             multiplier:0.0f
                               constant:100]];

[self.view3 addConstraint:
 [NSLayoutConstraint constraintWithItem:self.view3
                              attribute:NSLayoutAttributeHeight
                              relatedBy:NSLayoutRelationEqual
                                 toItem:nil
                              attribute:NSLayoutAttributeNotAnAttribute
                             multiplier:0.0f
                               constant:100]];

[self.view addConstraint:
 [NSLayoutConstraint constraintWithItem:self.view3
                              attribute:NSLayoutAttributeCenterX
                              relatedBy:NSLayoutRelationEqual
                                 toItem:self.view1
                              attribute:NSLayoutAttributeCenterX
                             multiplier:1.0f
                               constant:0]];

[self.view addConstraint:
 [NSLayoutConstraint constraintWithItem:self.view3
                              attribute:NSLayoutAttributeCenterY
                              relatedBy:NSLayoutRelationEqual
                                 toItem:self.view1
                              attribute:NSLayoutAttributeCenterY
                             multiplier:1.0f
                               constant:0]];

这段代码实现了约束view3的宽高为100,并且其中心位置在X/Y轴方向上居中于view1。

我们可以看到上面代码的实现效果图。红色为view1,蓝色为view2,绿色为view3。

扩展

  我们通过上面很简单的例子可以看到,虽然可以实现铜鼓约束来进行布局,但是代码还是比较冗长的,而且约束间关系也需要非常细心的思考才能确保约束的正确。
  实际上现在已经有很多开源的第三方库,它们都封装了AutoLayout,以达到简化代码量,提高代码复用性的目的。比如常用的有AutoLayoutMasonry等,接下来我们也会对这些第三方库进行一个学习。

源代码

这里给出的是Github上的源码地址。
iOS-Demon——AutoLayoutDemon

参考资料

  1. 苹果官方文档
  2. AutoLayout代码布局使用大全—一种全新的布局思想