咨询:13529513104

    本文是博主 iOS 开发实践系列中的一篇,主要讲述 iOS 中 Auto Layout(自动布局)在实际项目中的使用。

    Auto Layout 在 2012 年的 iOS 6 中发布,距今已经 2 年多了,如果从 2011 年在 Mac OS X 上发布的 Auto Layout 开始算起,已经超过 3 年了。如果你的简历上写着 2 年以上工作经验,而竟然不会使用 Auto Layout,真有点不可思议。

    本文将会通过若干个 Demo 进行讲解,通过实践来理解 Auto Layout 到底是什么,该如何使用(包括在 Xib 中使用以及手动编码)。

    Auto Layout 是什么?

    我的理解:Auto Layout 是一种基于约束的布局系统,它可以根据你在元素(对象)上设置的约束自动调整元素(对象)的位置和大小。

    官方的说明:

    

    Auto Layout 是一个系统,可以让你通过创建元素之间关系的数学描述来布局应用程序的用户界面。——《 Auto Layout Guide 》

    Auto Layout 是一种基于约束的,描述性的布局系统。——《 Taking Control of Auto Layout in Xcode 5 - WWDC 2013 》

    这里有几个关键字:

  •     
  •         

                元素         

        
    
  •         

                关系         

        
    
  •         

                约束         

        
    
  •         

                描述         

        

    元素(Element)

    低头看看你电脑的键盘,你可以把每一个按键当做一个元素;对于 iOS 系统来说,你可以把桌面上每一个应用图标当做一个元素;对于某一款 iOS 应用来说,你可以把视图中的每一个子视图当做一个元素。

    事实上,你也可以把整个键盘、桌面或者视图当做一个元素。

    关系(Relation)

    元素之间可以有关系。例如在键盘上 Q 键和 W 键之间有关系。是什么关系呢?有很多,例如 Q 键在 W 键的左边, W 键在 Q 键的右边, Q 键和 W 键之间相距 0.5 厘米等等。

    不理解?试着把键盘想象成 View ,把按键想象成 Button ,再思考一遍。

    约束(Constraint)

    元素之间关系的限制。约束是 Auto Layout 系统中最重要的概念。我们上面提到的 左边 、 右边 以及 相距 0.5 厘米 等这些都是约束,它们限制了元素之间的关系。

    描述(Description)

    定义约束来限制元素之间的关系。描述定义了元素之间的关系及约束。

    继续用键盘举例, Q 键的长宽均为 1 厘米,左边距离键盘的左边缘 10 厘米,上边距离键盘的顶部 5 厘米。 这句话就可以定位 Q 键在键盘中的位置,很轻松就可以计算出 Q 键的 frame 为 {{10.0, 5.0}, {1.0, 1.0}} 。

    现在 Q 键的坐标已经确定,那么 W 键的坐标可以这样描述: 顶部和 Q 键对齐,大小和 Q 键相等,位于 Q 键右侧 0.5 厘米处。 仔细想想,这句话中包含了元素间的关系,关系间的约束,可以直接计算出 W 键的 frame 。

    忘掉传统的 Springs & Struts 布局方式

    事实上如果你用传统的设置 frame 的布局方式的思维来理解上面的 Q 键和 W 键的布局也说的通。

    因为在 Auto Layout 中,当你描述完之后, Auto Layout 会自动帮你计算出 frame。换句话说,你的描述告诉了 Auto Layout 如何帮你计算出 frame。所以,你也可以理解为你间接的设置了 frame。为什么要这么做呢?为什么不直接设置 frame?这是因为使用 Auto Layout 有很多好处:

  •     
  •         

                多数情况下旋转屏幕不用再做额外的处理         

        
    
  •         

                更容易适配不同尺寸的屏幕         

        
    
  •         

                上手后布局非常简单容易,布局逻辑更清晰         

        

    Auto Layout 和传统布局很大的不同之处在于它是一种相对的布局方式。怎么理解这句话?上面提到

    

    W 键位于 Q 键右侧 0.5 厘米处。

    传统的布局无法直接表示,你必须把这种布局手动转换为传统布局代码。例如上面的 Q 键和 W 键的传统布局代码看起来可能是这样:

    q.frame = CGRectMake(CGRectGetMinX(keyBoard.frame) + 10.f, CGRectGetMinY(keyBoard.frame) + 5.f1.f1.f);w.frame = CGRectMake(CGRectGetMaxX(q.frame) + 0.5f, CGRectGetMinY(q.frame), CGRectGetWidth(q.frame), CGRectGetHeight(q.frame));

    使用 Auto Layout 的布局代码看起来像这样:

    // 伪代码q.width = 1.f;q.height = 1.f;q.left = keyboard.left + 10.f;q.top = keyboard.top + 5.f;w.top = q.top;w.width = q.width;w.height = q.height;w.left = q.right + .5f;

    Auto Layout 不仅能轻松表示这种布局,而且相对于传统的布局更清晰简洁易懂,还免费附赠很多优点,有什么理由不使用 Auto Layout 呢?

    实践中我发现对于很多新手来说,Auto Layout 这种布局方式比较容易理解接受,相反很多对传统布局很熟练的人却不太容易理解,总是用传统布局的思维来思考,所以如果可能的话,我建议你暂时忘掉传统的布局方式。

    Autoresizing Mask

    事实上我不打算讲这个东西,以及它和 Auto Layout 的区别和联系。如果你不知道,对学习 Auto Layout 不会有什么影响。

    你唯一需要注意的是在使用 Auto Layout 时,首先需要将视图的 translatesAutoresizingMaskIntoConstraints 属性设置为 NO 。这个属性默认为 YES ,如果你是使用 Xib 的话,这个属性会自动帮你设置为 NO 。当它为 YES 时,运行时系统会自动将 Autoresizing Mask 转换为 Auto Layout 的约束,这些约束很有可能会和我们自己添加的产生冲突。

    Auto Layout 基础知识

    无论是在 Xib 中还是代码中使用 Auto Layout,你都需要了解 Auto Layout 的一些必要知识。这些你现在不理解没有关系,后面我们会详细讲述。

    约束 (Constraint)

    Auto Layout 中约束对应的类为 NSLayoutConstraint ,一个 NSLayoutConstraint 实例代表一条约束。

    NSLayoutConstraint 有两个方法,第一个是

    + (id)constraintWithItem:(id)view1attribute:(NSLayoutAttribute)attribute1relatedBy:(NSLayoutRelation)relationtoItem:(id)view2attribute:(NSLayoutAttribute)attribute2multiplier:(CGFloat)multiplierconstant:(CGFloat)constant;

    不要被这个方法的参数吓到,实际上它只做一件事,就是让 view1 的某个 attribute 等于 view2 的某个 attribute 的 multiplier 倍加上 constant ,

    这里的 attribute 可以是上下左右宽高等等。

    精简后就是下面这个公式:

    view1.attribute1 = view2.attribute2 × multiplier + constant

    还有一个参数是 relation ,这是一个关系参数,它标明了上面这个公式两边的关系,它可以是 小于等于 (≤) , 等于 (=) 和 大于等于 (≥) 。上面的公式假定了这个参数传入的是 = ,根据参数的不同,公式中的关系符号也不同。

    需要注意的是,  或  优先会使用 = 关系,如果 = 不能满足,才会使用 < 或 > 。例如设置一个 ≥ 100 的关系,默认会是 100,当视图被拉伸时,100 无法被满足,尺寸才会变得更大。

    例子:

    1、我们要实现一个如下图的布局。

    

    布局代码如下:

    UIView *view = [UIView new];[view setBackgroundColor:[UIColor redColor]];[self.view addSubview:view];CGRect viewFrame = CGRectMake(50.f, 100.f, 150.f, 150.f);// 使用 AutoLayout 布局[view setTranslatesAutoresizingMaskIntoConstraints:NO];// `view` 的左边距离 `self.view` 的左边 50 点.NSLayoutConstraint *viewLeft = [NSLayoutConstraintconstraintWithItem:view                                                            attribute:NSLayoutAttributeLeadingrelatedBy:NSLayoutRelationEqualtoItem:self.view                                                            attribute:NSLayoutAttributeLeadingmultiplier:1constant:CGRectGetMinX(viewFrame)];// `view` 的顶部距离 `self.view` 的顶部 100 点.NSLayoutConstraint *viewTop = [NSLayoutConstraintconstraintWithItem:view                                                           attribute:NSLayoutAttributeToprelatedBy:NSLayoutRelationEqualtoItem:self.view                                                           attribute:NSLayoutAttributeTopmultiplier:1constant:CGRectGetMinY(viewFrame)];// `view` 的宽度 是 60 点.NSLayoutConstraint *viewWidth = [NSLayoutConstraintconstraintWithItem:view                                                             attribute:NSLayoutAttributeWidthrelatedBy:NSLayoutRelationGreaterThanOrEqualtoItem:nilattribute:NSLayoutAttributeNotAnAttributemultiplier:1constant:CGRectGetWidth(viewFrame)];// `view` 的高度是 60 点.NSLayoutConstraint *viewHeight = [NSLayoutConstraintconstraintWithItem:view                                                              attribute:NSLayoutAttributeHeightrelatedBy:NSLayoutRelationGreaterThanOrEqualtoItem:nilattribute:NSLayoutAttributeNotAnAttributemultiplier:1constant:CGRectGetHeight(viewFrame)];// 把约束添加到父视图上.[self.view addConstraints:@[viewLeft, viewTop, viewWidth, viewHeight]];

    实现一个如此简单的布局竟然要写这么多的代码,这显然难于推广使用。于是 UIKit 团队发明了另外一种更简便的表达方式进行布局,这个我们后面再讲,现在先看看这段代码。

    首先我把 view 的 translatesAutoresizingMaskIntoConstraints 设为了 NO,禁止将 Autoresizing Mask 转换为约束。

    然后在设置 viewLeft 这个约束时, attribute 参数使用了 NSLayoutAttributeLeading 而不是 NSLayoutAttributeLeft ,这两个参数值都表示左边,但它们之间的区别在于 NSLayoutAttributeLeft 永远表示左边,但 NSLayoutAttributeLeading 是根据习惯区分的,例如在某些文字从右向左阅读的地区,例如阿拉伯, NSLayoutAttributeLeading 表示右边。换句话说, NSLayoutAttributeLeading 是表示文字开始的方向。在英文、中文这种从左往右阅读的文字中它表示左边,在像阿拉伯语、希伯来语这种从右往左阅读的文字中它表示右边。通常情况下,除非你明确要限制在左边,否则你都应该使用 NSLayoutAttributeLeading 表示左边。相对的,表示右边也类似这样。这对于我们的本地化工作有很大的帮助。

    然后在设置 viewWidth 和 viewHeight 这两个约束时, relatedBy 参数使用的是 NSLayoutRelationGreaterThanOrEqual 而不是 NSLayoutRelationEqual 。

    因为 Auto Layout 是相对布局,所以通常你不应该直接设置宽度和高度这种固定不变的值,除非你很确定视图的宽度或高度需要保持不变。

    如果一定要设置高度或宽度,特别是宽度,在没有显式地设置内容压缩优先级(Content Hugging Priority,后面会讲到)和内容抗压缩优先级(Content Compression Resistance Priority,后面会讲到)的情况下,尽量不要使用 NSLayoutRelationEqual 这种绝对的关系,这会带来许多潜在的问题:

  •     
  •         

                根据内容决定宽度的视图,当内容改变时,外观尺寸无法做出正确的改变         

        
    
  •         

                在本地化时过长的文字无法显示,造成文字切断,或文字过短,宽度显得过宽,影响美观         

        
    
  •         

                添加了多余的约束时,约束之间冲突,无法显示正确的布局         

        

【责任编辑:(Top) 返回页面顶端