AutoLayout的坑

本文投稿文章,做者:MangoMade(简书api

AutoLayout很是强大也很是易用,可读性也很强,加上各类第三方AutoLayout库,让你布起局来犹如绷掉链子的狗!根本停不下来!之前的ide

1
label.frame.origin.y + label.frame.size.height + 10

现在只用:布局

1
2
3
button.snp_makeConstraints{
     $0.top.equalTo(label.snp_bottom).offset(10)
}

真是好用得不要不要。性能

但是,我在使用AutoLayout却遇到很多坑,翻阅了很多博客网站才找到了我认为的比较不错的解决方案。我把这些内容贴出来,若是其中有误,能够在下方留言指出,但愿你们可以多多交流共同进步。学习

本文主要分四部分:测试

  • updateViewConstraints与updateConstraints篇字体

  • AutoLayout与Frame篇动画

  • AutoLayout动画篇网站

  • AutoLayout比例设置ui

其中‘篇’字体现了本文做者对逼格的追求。

updateViewConstraints与updateConstraints篇

基本用法

updateViewConstraints与updateConstraints是AutoLayout出现后新增的api,updateConstraints主要功能是更新view的约束,并会调用其全部子视图的该方法去更新约束。

而updateViewConstraints的出现方便了viewController,不用专门去重写controller的view,当view的updateConstraints被调用时,该view如有controller,该controller的updateViewConstraints便会被调用。

两个方法都须要在方法实现的最后调用父类的该方法。而且这两个方法不建议直接调用。

在使用过程当中我发现这两个方法有时候不会被系统调用。后来我看到public class func requiresConstraintBasedLayout() -> Bool方法的描述:

constraint-based layout engages lazily when someone tries to use it (e.g., adds a constraint to a view). If you do all of your constraint set up in -updateConstraints, you might never even receive updateConstraints if no one makes a constraint. To fix this chicken and egg problem, override this method to return YES if your view needs the window to use constraint-based layout.

大意是说,视图并非主动采用constraint-based的。在非constraint-based的状况下-updateConstraints,可能一次都不会被调用,解决这个问题须要重写该类方法并返回true。

这里要注意,若是一个view或controller是由interface builder初始化的,那么这个实例的updateViewConstraints或updateConstraints方法便会被系统自动调用,起缘由应该就是对应的requiresConstraintBasedLayout方法返回true。而纯代码初始化的视图requiresConstraintBasedLayout方法默认返回false。

因此在纯代码自定义一个view时,想把约束写在updateConstraints方法中,就必定要重写requiresConstraintBasedLayout方法,返回true。

至于纯代码写的viewController如何让其updateViewConstraints方法被调用。我本身的解决办法是手动调用其view的setNeedsUpdateConstraints方法。

How to use updateConstraints?

文档中对于这两个方法提的最多的就是,重写这两个方法,在里面设置约束。因此一开始我认为这两个方法是苹果提供给咱们专门写约束的。因而便开始尝试使用。

直到后来在UIView中看到这样一句话:

You should only override this method when changing constraints in place is too slow, or when a view is producing a number of redundant changes.

“你只因该在添加约束过于慢的时候,或者一次要修改大量约束的状况下重写次方法。”

简直是让人以为又迷茫又坑爹。updateConstraints方法到底应该什么时候使用

后来看到how to use updateConstraints这篇文章。给出了一个合理的解释:

  • 尽可能将约束的添加写到相似于viewDidLoad的方法中。

  • updateConstraints并不该该用来给视图添加约束,它更适合用于周期性地更新视图的约束,或者在添加约束过于消耗性能的状况下将约束写到该方法中。

  • 当咱们在响应事件时(例如点击按钮时)对约束的修改若是写到updateConstraints中,会让代码的可读性很是差。

关于性能,我也作了一个简单的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class MMView: UIView {
     override init(frame: CGRect) {
         super .init(frame: frame)
         self.backgroundColor = UIColor.grayColor()
         initManyButton()
         //初始化时添加约束
         test()  //每次只有一个test()不被注释就好
     }
     override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
         //响应事件时添加约束
         //test()
     }
     override func updateConstraints() {
         //updateConstraints中添加约束
         //test()
         super .updateConstraints()
     }
     func test(){
         let then = CFAbsoluteTimeGetCurrent()
         addConstraintsToButton()
         let now = CFAbsoluteTimeGetCurrent()
         print(now - then)
     }
     required init?(coder aDecoder: NSCoder) {
         fatalError( "init(coder:) has not been implemented" )
     }
     let buttonTag = 200
     func initManyButton(){
         for  index  in  0...1000{
             let button = UIButton(type: .System)
             button.tag = buttonTag + index
             self.addSubview(button)
         }
     }
     func addConstraintsToButton(){
         for  index  in  0...1000{
             if  let button = self.viewWithTag(index+buttonTag){
                 button.snp_makeConstraints{ make  in
                     make.center.equalTo(self)
                     make.size.equalTo(self)
                 }
             }
         }
     }
}

分别将设置约束写在init中、写在updateConstraints中、写在事件响应方法中 的时间消耗进行测试,对1000个button添加约束,每一个添加4个约束。

  • init中,时间消耗约为0.37秒

  • 写在updateconstraints中,时间消耗约为0.52秒

  • 写在事件响应方法中,时间消耗约为0.77秒

因此,结论,仍是将约束的设置写在viewDidLoad中或者init中。没事儿尽可能不去碰updateConstraints。除非对性能有要求。

关于UIView的translatesAutoresizingMaskIntoConstraints属性

最近在对AutoLayout的学习中发现,不少人彷佛对translatesAutoresizingMaskIntoConstraints的误解很是大,不少时候遇到问题总有人会在下面回答到:把translatesAutoresizingMaskIntoConstraints设置成false就能够解决问题。。。实际上并无什么用。

那么这个属性究竟是作什么的呢?

其实这个属性的命名已经把这个属性的功能解释的很是清楚了。

除了AutoLayout,AutoresizingMask也是一种布局方式。这个想必你们都有了解。默认状况下,translatesAutoresizingMaskIntoConstraints = true , 此时视图的AutoresizingMask会被转换成对应效果的约束。这样极可能就会和咱们手动添加的其它约束有冲突。此属性设置成false时,AutoresizingMask就不会变成约束。也就是说 当前 视图的 AutoresizingMask失效了。

那咱们何时须要设置这个属性呢?

当咱们用代码添加视图时,视图的translatesAutoresizingMaskIntoConstraints属性默认为true,但是AutoresizingMask属性默认会被设置成.None。也就是说若是咱们不去动AutoresizingMask,那么AutoresizingMask就不会对约束产生影响。

当咱们使用interface builder添加视图时,AutoresizingMask虽然会被设置成非.None,可是translatesAutoresizingMaskIntoConstraints默认被设置成了false。因此也不会有冲突。

反而有的视图是靠AutoresizingMask布局的,当咱们修改了translatesAutoresizingMaskIntoConstraints后会让视图失去约束,走投无路。例如我自定义转场时就遇到了这样的问题,转场后的视图并不在视图的正中间。

因此,这个属性,基本上咱们也不用设置它。

AutoLayout与Frame篇

在使用AutoLayout的时候你可能也会同时也会用到frame,好比须要用到layer的时候。

那么你可能会遇到这种状况,想让layer的尺寸是由其它视图尺寸设定的,而这个视图又是由约束控制布局的。若是将layer的初始化与view的初始化放在一个方法中,相似于viewDidLoad的方法中

1
layer.bounds = CGRectMake(0,0,view.bounds.size.widith * 0.5,50)

那么极可能最终layer的宽度是0。

这是由于约束被设置以后它并不会当即对view做出改变,而是要等到layout时,才会对视图的尺寸进行修改。而layout一般是在视图已经加载到父视上时。

因此咱们若是在viewDidLoad中设置了约束,要等到viewDidAppear时view的尺寸才会真正改变。

那么,若是须要既用约束布局,又用frame布局,若是能让它们很好的协做呢?

一个很好的解决办法是:吧frame设置写到layoutSubviews中或者写到viewDidLayoutSubviews中便可。由于约束生效时view的center或者bounds就会被修改,center或者bounds被修改时layoutSubview,就会被调用,随后viewDidLayoutSubviews就回被调用。这个时候,设置约束的视图frame就再也不是(0,0,0,0)了

若是咱们必需要将约束和frame写在同一方法中,写完约束就设置frame,而不是想把frame的设置写到layoutSubview中(好比咱们设置好约束后立刻就想根据约束的结果计算高度),那么咱们还能够在设置完约束以后手动调用layoutIfNeeded方法,让视图当即layout,更新frame。在这以后就能够拿到设置约束的视图的尺寸了。

AutoLayout动画篇

这篇的内容很是简单,就是介绍约束布局的视图如何进行位移动画。

若是咱们的一个视图是经过设置frame来布局的,那么咱们在位移动画时直接改变frame就能够了。很简单。

但是在约束布局的视图中,设置frame这个办法就无效了。那咱们怎么办?

网上有不少人的办法就是:拿到想要作动画的约束,在动画以前对约束进行修改,在动画的block中调用setNeedsLayout方法。

这个方法我以为很是的麻烦,为了方便地拿到约束,咱们一般还须要把约束设置成属性,动画一多那岂不就是完蛋了?

一种更好的方法就是设置视图的transform属性。

好比我想要让视图作一个x轴+50的位移,

1
self.view.transform = CGAffineTransformMakeTranslation(50, 0)

这样设置便可。CGAffineTransformMakeTranslation这个方法就是设置位置。

AutoLayout比例设置

若是咱们用autoLayout想把一个视图的中心设置到屏幕横向和纵向的1/4处:

1
2
3
4
button.snp_makeConstraints{ make  in
     make.centerX.equalTo(self.view).multipliedBy(0.25)
     make.centerY.equalTo(self.view).multipliedBy(0.25)
}

这就至关于

1
button.center = CGPointMake(self.view.bounds.size.width * 0.25 ,self.view.bounds.size.height * 0.25)

那么AutoLayout中的倍数,具体表示什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let view = UIView()
self.view.addSubview(view)
var  bottomConstraint : Constraint!
view.snp_makeConstraints { (make)  in
     make.height.equalTo(50)
     make.width.equalTo(50)
     make.centerX.equalTo(self.view.snp_centerX)
     bottomConstraint = make.bottom.equalTo(self.view.snp_centerY).constraint
}
self.view.layoutIfNeeded()
print(view.frame)
//打印结果 y:318 height:50 和为368
bottomConstraint.uninstall()
view.snp_makeConstraints { (make)  in
     make.bottom.equalTo(self.view.snp_centerY).multipliedBy(1.5)
}
self.view.layoutIfNeeded()
print(view.frame)
//打印结果 y:318 height:50 和为552,恰好是368的1.5倍
//因此咱们能够得出结论:某条边的约束的倍数表明着这条边到相对边的距离的倍数
//上面代码中的1.5倍让bottom边到y = 0边的距离变成了1.5倍
相关文章
相关标签/搜索