iOS AutoLayout学习总结

由于项目一直在做iOS5方面的支持,没法使用最新的技术,这个真的有些遗憾,尤其是iOS6中引入的UIAutolayout和UICollectionView,以及iOS7中的TextKit。最近项目事情比较少,准备合适的时候,引入iOS6中的新技术。让这边的同事做了一下关于UIAutolayout方面的技术预研,做的还不错。他说有一些问题没法解决,我就自己花时间也看了一下。看了之后,感觉这个技术是挺强大的,不过就是感觉不够清晰,单纯的VFL或者单纯的OC都行,但是把二者融合到一起就有些别扭,还有一些地方讲解的不是特别清晰,使用的时候就导致一些问题的出现。


=============================================================================================

一. Auto Layout概念

1. Constraint基础

Constraint(约束)是auto layout的基础概念,可以在运行时自动布局UI元素。 我们可以通过对View的属性的设置来生成约束,这些属性有: left, right, top, bottom, leading, trailing, width, height, centerX, centerY, baseline。除了View的这些属性,在constraints中还有一些特性可以设置:
1) constant value: 常量值,比如设置属性的值为某个常量
2) relation: 关系,对于view的属性值不仅仅可以设置为某个常量,还可以通过关系操作符来设置
3) priority level: 优先级,优先级高的约束在进行布局的时候会优先被满足

2. Intrinsic Content Size

Leaf-level View(也就是继承于View的UIKit控件,比如button和label)可以更加明确它们展示内容所需要的size,而不是那些用来定位它们的代码。之前我们一般情况下很难知道展示这个button需要多大的size,需要设计师来标注每个控件的具体尺寸,而在auto layout中,我们可以不用再指定这些view的size,只需要指定他们的position就可以了,转而由这些View自身来告诉layout system他们的size是多大,而intrinsic content size就是这些View用来告诉layout system他们自身content所需要的size的。 这样设置这些View的尺寸,肯定比我们根据设计图来设置更加合适,也会大大减少我们自身所做的工作量。

如果实现custom view的时候,我们并非所有的view都需要指定这个intrinsic content size,而就我自己的经验而言,我自己常实现的view的封装,基本不要设置intrinsic content size。只有那些布局系统无法理解的内容,才需要设置这个值,比如说自己实现一个使用quartzcore框架绘制一个控件或者coretext来实现一个底层的label,这些时候layout system肯定无法知道你这个custom view的content需要多大的size,这时候你需要指定这个值才行,不然只能去设置一个常量值来设置宽和高。

而我自己经常实现的view,更像是android中所说的组合view,也对利用UIKit现有的控件进行包装,比如自定义tab之类的。在auto layout中,我们只需要在这些自定义view中设置constraint就行,layout system会根据这些约束来设置这个view的尺寸,比如设置自定义view中的子view的宽高以及这些子view和这个自定义view的间距等约束,而这些子view使用的系统的控件,所以大多数会有intrinsic content size,layout system通过添加的那些约束,就能确定这个自定义view的尺寸了。对于这些不需要指定intrinsic content size的view,View的- (CGSize)intrinsicContentSize方法会返回{ NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric }。

二. 使用

上面讲的是我比较困惑的关于auto layout的概念,其实理解了这些概念,auto layout使用起来非常容易掌握,整个学习过程用不了多久,只是对于里面的过程官方文档说的不多,比如layout system进行layout的过程,再加上不开源。所以有时会遇到很多奇怪的问题,这个让我理解起来感觉有些麻烦。其实auto layout我理解起来很费劲,但是在具体使用API的时候,却是非常简单,这个和OC本身的"啰嗦"有关系,你甚至不需要看API文档就知道是什么意思。

1. NSLayoutConstraint接口

整个auto layout的所有核心是constraint,添加约束其实只有两个方法,而对约束我的理解就是确定view的属性,这样一来一会就能学会使用auto layout。
a).  + (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
b).  +(id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

关于这两个接口,我觉得有几点需要注意的地方
1.  a方法是不单单只是生成一个NSLayoutConstraint,而是生成一批NSLayoutConstraint对象。这个我认为本质上,是通过VFL这种DSL语言来简化整个auto layout API,不然你就需要全部使用b方法一个一个去生成这些NSLayoutConstraint对象,会非常繁琐。
2. b方法则能实现很多a无法实现的约束,也就是说b方法比VFL更加全面,是a方法的超集。比如一些跨属性或较复杂的关系的约束,类似实现一个View的高度是另外一个View宽度的2倍,这个使用VFL就无法实现,但是用b方法却是非常简单。

2. VFL语言

关于apple提供的VFL语言,看完文档才知道,VFL能做的事情其实是很有限的。而且很重要的一点是VFL甚至不是必须的,这个跟我一开始理解是冲突的,之前我认为Auto layout的基础是VFL,其实不是的,VFL的基础是为view的属性设置constraint,VFL最终也需要解析为constraint才行,而不是constraint变为VFL。这样以来,就很容易理解了VFL了,它语法非常简单,能做的事情非常有限。Apple关于auto layout的guide上面的文档已经把这一部分讲解的很清楚了,关于语法,实际有耐心看一下就明白这个图的意思了。有一些地方解释一下,"?"表示前面的内容不是必须,"*"表示前面的内容可以重复出现。
iOS AutoLayout学习总结_第1张图片


3. 注意点

学习过程中,还有几个地方很有意思。

a). UIView的- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);方法,这个方式一开始不知道是干什么的,看了几篇文章都讲的不清楚,后来才看到这个是用来Auto layout 中用来计算View的size的,比如在获取某个cell的高度,就可以使用TableViewCell中contentView来调用这个方法,获取一个尺寸可以获取cell的高度。
b). 调用上面的UIView中的方法获取的size和intrinsic size有什么关系呢?这个我挺纳闷的,但是很可惜,没有找到相应的文档,官方认为这两个值一般是不会同时出现的。根据UIView头文件中的注释,官方认为只有那些native也就是layout system无法理解的时候,才需要设置intrinsic size,什么时候无法理解呢,就是类似Button,label,imageview这类UIKit控件,它们内部并非一个个的UIKit控件组合而成,而是绘制出来的。也就是说一般情况下,设置intrinsic size的View是没有子view的。我自己试了一下,发现如果同时设置intrinsic size和通过调用systemLayoutSizeFittingSize来让layout system获取的size,这是宽和高都会使用这两个中间较大的值。
c). 写个demo的时候,发现一个很有意思的问题,使用一个UIView称为 centerView做superview,内部放有redView和blueView,通过下面两种方式设置constraints,但是很奇怪无论如何改变,centerView的高度始终为0,但是宽度却能正确设置。
" |-0-[redView]-30-[blueView]-0-|"
" V:|-0-[redView]"
" V:|-0-[blueView(==redView)]"
看了好一会,最后发现越来并没有设置redView和blueView的边界和centerView之间的约束,这样的约束在计算的时候,就无法确定centerView的高度,所以centerView的高度一直是0,但是没有设置centerView的clipToBounds为YES,所以redView和blueView仍然可以显示。讲约束改为:
" |-0-[redView]-30-[blueView]-0-|"
" V:|-0-[redView]-0|"
" V:|-0-[blueView(==redView)]-0-|"

4. AutoLayout与非AutoLayout的结合

这点我一开始很困惑,后来看了官方guide最后一部分,然后又自己做了测试,才明白是怎么回事,关键是UIView的translatesAutoresizingMaskIntoConstraints的属性,这个属性默认为YES,也就是说默认是无法使用AutoLayout来设置的。当这个值设置为NO设置AutoLayout才会起作用,并且这个时候,如果同时也设置了Frame和AutoResizingMask的话,AutoLayout会起作用,设置的Frame不会起作用。并且这个值不会影响subview的auto layout的使用,比如直接设置superview的setFrame来控制Frame,但是subviews可以使用autolayout的,这个在示例代码中有。


github示例代码地址:https://github.com/lihei12345/AutoLayoutDemo

三. 总结

其实到最后,发现这些技术要是耐着性子去理解,都不是很难的技术,总可以搞定,还是自己不够有耐心的问题。
这个技术的关键点是理解约束,一切都是围绕约束进行的。有了约束,每个View就可以实现self control,自己来确定自己的高度,然后在进行布局。这个Android中View的布局是类似的,都是分散看来让View自我控制尺寸。只是iOS的这个,某种程度上来说使用更为简单,因为API接口较少,但是理解较为困难,资料还是有些不足,不过满足开发使用应该就够了。



=============================================================================================

参考:

  1. https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/Introduction/Introduction.html
  2. http://stackoverflow.com/questions/18214048/resizing-the-superview-according-to-the-subviews
  3. http://stackoverflow.com/questions/19170053/resize-superview-after-subviews-change-dynamically-using-autolayout
  4. http://corecocoa.wordpress.com/2013/11/09/auto-growing-uitextview-using-auto-layout/
  5. http://stackoverflow.com/questions/18118021/how-to-resize-superview-to-fit-all-subviews-with-autolayout/18155803#18155803
  6. http://stackoverflow.com/questions/13186908/can-i-use-setframe-and-autolayout-on-the-same-view/13539782?s=3|2.5377#13539782
  7. http://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights/18746930?s=1|5.0447#18746930

你可能感兴趣的:(iOS)