WWDC 2015 - 揭开AutoLayout的神秘面纱(Mysteries Of Auto Layout) Part2

原文地址:http://www.pluto-y.com/wwdc-2015-mystries-of-auto-layout/
继续前一篇文章所讲的技巧,接下来将剩下的六个技巧。

技能点七:Layout的周期

关于布局周期这里主要是针对修改布局的情况下,其实主要就分为以下三个步骤,并且这三个步骤之间形成一个循环。具体如下:
Layout Cycle
先是约束变化,然后会产生一个延迟的布局事件,最后到达App的循环。而其中App的循环又会继续等待约束变化的过程,从而产生一个新的周期。

约束变化

针对这里的约束变化主要包括以下几种:
* Activating和Deactivating约束
* 设置约束的constant或者priority
* 添加或者删除视图
以上这几种的约束都会产生一个新的布局周期。会根据以下的变化重新计算布局,Layout的引擎则会根据计算的结果接收到新的布局变量,而视图就会自动去调用superview.setNeedsLayout()。这些步骤就组成了约束变化的过程。

延迟的布局事件

延迟布局的通过主要包括以下几个步骤:
* 重新放置那些错放的视图
* 更新约束并且重新给视图的frame赋值

当然除了约束变化会导致延迟布局的事件以外,还可以通过setNeedsUpdateConstraints()来触发布局事件。

并且在苹果官网还是强烈建议使用Activating ContraintsDeactivating Contraints而不是去Change Contraints(即Add 或者Remove约束),毕竟前者比后者来说说速度快多了。小伙伴们以后要习惯用Activating 和 Deactivating来修改约束。

关于layoutSubviews(在Mac OX中叫做layout)的方法的作用主要在于让布局引擎接受到新的布局的约束值,而不是重新排布各个视图的位置。并且将新的值设定到各个子视图中(在iOS中用的是setBoundssetCenter在Mac OX中用的是setFrame)
而如果在重写layoutSubviews的过程中需要注意以下几点:
* 必须调用[super layoutSubviews]的方法
* 修改视图的布局需要在调用[super layoutSubviews]之前进行修改
* 别调用setNeedsUpdateConstraints
* 别修改不是属于该视图的子视图
* 别随意的修改约束

然而这个技能点主要是讲解了一些理论上的知识,而博主对此也不是有非常深刻的理解,所以可能讲解的有些不是特别好,望大家见谅。不过一些注意点是大家都需要注意的,麻烦大家记一下。

技能点八:与之前的适配进行交互

对于此来说,iOS关于布局最先是通过setFrame来进行布局,之后在此基础上添加了一个autoresizingMask来进行根据父视图的修改来调整子视图的布局,最后在近几个版本中添加了一个Auto Layout的布局机制,主要是通过约束来进行布局。而技能点八主要是针对那些就的布局机制与AutoLayout共存情况下的一些情况的注意点。

当然在Auto Layout下,小伙伴们依然可以用setFrame来进行布局,但是在之后可能会有个延迟布局的事件来回调的情况下,可能您的setFrame会出现失效的情况。然后对于那些小伙伴们真的希望通过setFrame来进行布局的控件来说苹果有提供一个变量进行处理该种情况。这个变量就是translatesAutoresizingMaskIntoConstraints,如果在创建某个视图是通过IB的情况下,它的默认值就是NO,而如果在创建某个视图是通过代码的情况下,它的默认值则就是YES。而如果小伙伴们想在代码中添加或声明约束给代码生产的视图的话,即的将这个变量设置为NO,否会出现报错的情况,如下图的情况下:
translatesAutoresizingMaskIntoConstraints Demo

其实只需要将translatesAutoresizingMaskIntoConstraints设置成NO即可。正确的代码如下:

    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    //别忘了将这个变量设置成NO
    btn.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:btn];
    NSLayoutConstraint *kTopContraints = [NSLayoutConstraint constraintWithItem:btn attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:10];
    NSLayoutConstraint *kLeadingContraints = [NSLayoutConstraint constraintWithItem:btn attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:10];
    [NSLayoutConstraint activateConstraints:@[kTopContraints, kLeadingContraints]];

然而其实将这个变量设置成YES后,对于程序来说,可以做以下操作:
* 任意的通过setFrame进行布局
* 可以用autoresizingMask来实现约束
* 其他的视图可以与该视图建立约束

技能点九:约束的创建

关于约束的创建来说(针对代码创建),其实在iOS 9 中主要是优化了之前方法的可读性。简化了之前繁琐的创建方法,使方法更加简洁与可读。废话不多说,上代码。
对于以前来说要创建约束需要用一下的方法:

    [NSLayoutConstraint constraintWithItem:self.saveButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0 constant:100];
    [NSLayoutConstraint constraintWithItem:self.saveButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];

其实从代码层面上来说,这种方式过于复杂与繁琐。而在新的sdk中添加了简化的方法,如下:

    [self.saveButton.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:100];
    [self.saveButton.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:100];

相对上面的方法来说一目了然。当然在iOS9中添加了很多Anchor的属性,大部分针对NSLayoutAttribute都有其对应的属性。
然而对于这种新的SDK来说也有一些需要注意的地方:
* 不要给位置的Anchor设置成一个常量
如:[v1.leadingAnchor constraintEqualToConstant:100];这种做法是不允许的。
* 不要给位置的Anchor设置成一个Size的Anchor
如: [v1.leadingAnchor constraintEqualToAnchor:v2.widthAnchor];同样是不被允许的。

技能点十:Layout Guide

首先先举一个例子,例如在一个场景下,我们需要3个Button,而三个Button中间的距离需要保持相等的距离。而在以往的情况下需要在Button中间建立View并且用约束来保证Button与Button之间的距离保持一致。如下图所示:
LayoutGuide所需效果图
然后其实对于中间的两个View来说,其实这两个View是完全没有实际的意义,只是为了保证Button与Button之间的距离是一致的作用。
而对于新的SDK中添加了新的类用于处理该功能,即UILayoutGuide(Mac OX中为NSLayoutGuide)。UILayoutGuide在官方文档中说了,他是定义了一个正方形的区域用于Auto Layout的机制。其用法也相当简单,可以将其看做上图中的无意义的视图。并且其也拥有跟视图一样的约束的操作。或许我们就可以将UILayoutGuide看做是一个特殊的视图即可。
具体实现代码如下:(博主用Save、Cancel和Clear三个按钮用来替换)

    UILayoutGuide *space1 = [[UILayoutGuide alloc] init];
    [self.view addLayoutGuide:space1];
    UILayoutGuide *space2 = [[UILayoutGuide alloc] init];
    [self.view addLayoutGuide:space2];
    [space1.widthAnchor constraintEqualToAnchor:space2.widthAnchor].active = YES;
    [self.saveButton.trailingAnchor constraintEqualToAnchor:space1.leadingAnchor].active = YES;
    [self.cancelButton.leadingAnchor constraintEqualToAnchor:space1.trailingAnchor].active = YES;
    [self.cancelButton.trailingAnchor constraintEqualToAnchor:space2.leadingAnchor].active = YES;
    [self.clearButton.leadingAnchor constraintEqualToAnchor:space2.trailingAnchor].active = YES;

具体效果如下:(左图为IB中的图,右图为模拟器的运行效果)
代码运行效果图

技能点十一:布局的调试技巧

关于布局的调试功能,博主就准备用于官方类似的Demo进行讲解。具体如下截图的布局:
Debug效果图
然而在该例子中由于配置不当会导致运行是出现Layout的错误,而利用该问题中导致的错误来说如何调试布局中出现的问题。而在运行过程中导致的问题的输出如下:

2015-10-13 00:57:46.025 WWDC15AutoLayoutMysteries[13103:4654603] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7fb999710a70 H:[animalImg]-(122)-| (Names: animalImg:0x7fb999659790, '|':UIView:0x7fb999659630 )>",
    "<NSLayoutConstraint:0x7fb999710ac0 H:|-(123)-[animalImg] (Names: animalImg:0x7fb999659790, '|':UIView:0x7fb999659630 )>",
    "<NSLayoutConstraint:0x7fb999659950 'imgAspectRatio' animalImg.width == 1.5*animalImg.height (Names: animalImg:0x7fb999659790 )>",
    "<NSLayoutConstraint:0x7fb999710cf0 'imgTopMargin' V:|-(21)-[animalImg] (Names: animalImg:0x7fb999659790, '|':UIView:0x7fb999659630 )>",
    "<NSLayoutConstraint:0x7fb999710d60 'labelTopMargin' V:|-(100)-[questionLbl] (Names: questionLbl:0x7fb99965ec60, '|':UIView:0x7fb999659630 )>",
    "<NSLayoutConstraint:0x7fb999710dd0 'labelTopToImg' V:[animalImg]-(8)-[questionLbl] (Names: questionLbl:0x7fb99965ec60, animalImg:0x7fb999659790 )>",
    "<NSLayoutConstraint:0x7fb999587c60 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7fb999659630(320)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7fb999659950 'imgAspectRatio' animalImg.width == 1.5*animalImg.height   (Names: animalImg:0x7fb999659790 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

对于大部分人来说看到这么多的输出马上会感到害怕,甚至会不假思索的马上将结果复制到百度或者google中去搜索,希望直接从别人的历史记录中得到答案,然而本王想说的是,不要害怕,让本王告诉你如何快速定位问题。

遇到这种问题,首先先去确定该问题是不是由于前面所说的translatesAutoresizingMaskIntoConstraints没有设置成正确的值所导致的,具体设置查看上面的教程,毕竟很多情况下有人会忘记将其设置成正确的值。
如果在确认了不是由于上述问题所导致的话,接下来就直接去查看Log的最后一句,即Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7fb999659950 'imgAspectRatio' animalImg.width == 1.5*animalImg.height (Names: animalImg:0x7fb999659790 )>
,可以看出肯定是由于哪个约束与这个约束产生了冲突而导致。接下来就对Log中的每个约束进行绘制到纸上,查看可能哪个约束与其冲突。这里就采用一下官方PDF中的截图,与Demo中的布局类似,大家可以借鉴一下。
绘制约束
可以看得出来Label的TopMargin与ImgTopMargin+ImgHeight+ImgToLabelMargin的之间会产生冲突,而其实应该将Label与父视图的上边距应该进行去除。

然而如果是细心的朋友可以看到在上面的输出语句中可以看到animalImglabelTopMarginimgAspectRatio等一些一看就知起意的命名,而如果正常情况下的话应该不会出现有这些命名的存在。因此这里需要提一个技巧,即给对应的视图和约束设置identifier,而这些identifier则会在出现错误输出更友好的输出,保证程序猿们更好的读懂错误的Log。
而对于identifier在IB上也可以设置,也可以通过identifier的属性进行设置。官方推荐给视图、约束以及LayoutGuide设置identifier。而对于在VFL的约束,则需要通过遍历来设置identifier。
同时如果在约束过多的情况下,可以通过constraintsAffectingLayoutForAxis(Max OX中方法名为constraintsAffectingLayoutForOrientation)该方法来进行过滤,该方法是用来显示一个方向的约束。0则显示水平方向的约束,而1的情况下显示垂直方向的约束。看看我们的苹果是如此的贴心。

这里就总结一下,主要的技巧包括以下几个:
* 如果出现错误的Log,则从Log的底部进行开始读
* 确认translatesAutoresizingMaskIntoConstraints的变量是否设置正确
* 给所有的视图、约束以及LayoutGuide的设置对应的identifier,方便之后出现错误日志是方便阅读
* 利用constraintsAffectingLayoutForAxis进行过滤过多的约束

技能点十二:处理模糊的布局

关于出现模糊的布局的可能情况主要包括以下几种:
* 约束不足或者可以说是缺少约束
* 优先级的冲突,即可能都在使用默认的优先级(而关于其中的优先级的详细内容可以查看上面的技能点)

而如果想要解决布局的模糊可以通过一下的工具进行处理:
* 在IB中出现红色和黄色的图标,即出现布局上的Error和Warmming
* 使用_autolayoutTrace方法进行查看视图的视图是否有Ambigous的Layout,即是否出现模糊的布局,该方法需要在LLDB中进行使用
* 而其中最后的方法则是通过菜单中的Debug->ViewDebug->Show Alignment Rectangles来进行查看视图
* 其中在设置断点的情况,请断点暂停了,则可以通过icon button按钮来进行查看视图的层次
* 除了上述的_autolayoutTrace方法以外,还有另一个方法exerciseAmbiguityInLayout进行多次运行则可看到由于模糊的情况下Layout Engine可以给我们提供的多种情况中来判断是由于什么原因到只的视图模糊

关于调试的细节这里就希望各位童鞋自己去多尝试尝试了,如果有什么问题的话可以通过留言、邮件以及通过@叫什么都不如叫Pluto-Y来进行提问。博主会尽其所能为您解答。

到此为止Part2就好了。如果上篇还没看的同学可以去查看上篇:WWDC 2015 - 揭开AutoLayout的神秘面纱(Mysteries Of Auto Layout) Part1

而希望一次看到完整版的可以进入我的博客:http://www.pluto-y.com查看文章,并且文章中有Github的Demo下载地址,如果下载积分不够的童鞋可以去我博客里面找下载地址。

而CSDN的Demo下载地址为:Demo源码地址,该源码中包含了Part1和Part2的Demo。有不理解的可以进行留言。

转载于:https://my.oschina.net/youtk21ai/blog/540710

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值