AutoLayout的坑

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

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


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比例设置

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

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倍

原文地址:https://www.cnblogs.com/oc-bowen/p/8969214.html

时间: 2024-10-13 19:31:31

AutoLayout的坑的相关文章

一些Layout的坑。坑死我自己了

iOS这个东西,初学感觉,还好还好,然后一年之后再来修复一下初学的时候的代码,我只是感觉头很晕- - 别扶我. AutoLayout的坑,明明以前都没有的!!!升了iOS10就突然发现了这个坑,其实也有可能是以前就有,只是没踩到... 正点来了 当以前的我使用StoryBoard制定一系列的约束的时候,感觉屏幕适配都不是问题了! 然后以前的我突发奇想,不行,我要加一个代码控件,但是以前的我哪知道AutoLayout这个东西啊. 然后,就手写了个TextView,然后frame:CGRectMak

一些Layout的坑

iOS这个东西,初学感觉,还好还好,然后一年之后再来修复一下初学的时候的代码,我只是感觉头很晕- - 别扶我. AutoLayout的坑,明明以前都没有的!!!升了iOS10就突然发现了这个坑,其实也有可能是以前就有,只是没踩到... 正点来了 当以前的我使用StoryBoard制定一系列的约束的时候,感觉屏幕适配都不是问题了! 然后以前的我突发奇想,不行,我要加一个代码控件,但是以前的我哪知道AutoLayout这个东西啊. 然后,就手写了个TextView,然后frame:CGRectMak

autolayout的各种坑

[_xibView mas_updateConstraints:^(MASConstraintMaker *make) { make.top.offset(300); make.width.offset(100); make.height.offset(100); }]; 1. autolayout做动画需要调用系统方法 [self.view layoutIfNeeded]; 例如: [UIView animateWithDuration:0.5 animations:^{ // CGRect

前两日遇到的一个关于AutoLayout的一个坑

对于一个view,先将其add到一个superView上,并设置约束,比如,设置高度约束为100 如果再次操作这个view,将其添加到另一个superView上,再次设置约束时,之前附加到该view上的约束,并不会因为其superView变化而清空,就会出现约束冲突. 如何解决: 再次操作添加约束时,应先将之前的约束清空,对于masonry来说,就是使用remakeConstraint代替makeConstraints即可

AutoLayout的那些事儿

转自:http://www.cocoachina.com/ios/20160530/16522.html 本文投稿文章,作者:MangoMade(简书) AutoLayout非常强大也非常易用,可读性也很强,加上各种第三方AutoLayout库,让你布起局来犹如绷掉链子的狗!根本停不下来!以前的 label.frame.origin.y + label.frame.size.height + 10 如今只用: button.snp_makeConstraints{     $0.top.equa

使用 autolayout 在scrolleView中进行布局

相信有很多人因为iPhone6出来后 ,因为适配而选择开始使用autolayout.现在正在被虐中...    今天要做一个在scrollView上面 可以滚动的效果  平时用代码写 感觉很快  , 被autolyout 坑死了 .  弄了好久才知道为什么 scrollView 不能滚动. 话不多说  首先创建一个新的工程 .然后往view 上面加载一个scrollView 并加好约束 . 这里是让他铺满整个屏幕.没什么技巧 . 然后 ,我就想是不是设置下 contentSize  就行了  .

ios 加载xib遇到的坑

storyboard,个人觉得是个好玩意儿,但是什么都做到其中总觉得杂乱.个人偏好把复杂的局部控件(比如定制的collectionviewcell)在xib文件中拉好. 在开发过程中遇到不少坑,记忆犹新的是:xib中的部件(比如button)设置圆角的效果不对:加载的xib不能resize大小. 第一个问题: 圆角的设置代码: view.layer.cornerRadius = view.frame.size.height / 2; 使用了autolayout则需要注意调用的地方:(因为auto

Autolayout 约束类方法的理解(学习笔记二)

随apple大屏手机的问世,屏幕适配问题被堆到风口浪尖,对于代码画UI的同学无疑是个噩梦.在上班闲暇之余,学习了autolayout:autolayout从iOS6开始使用,因为各种坑,只有一些advanced coder们所用,但现在autolayout今非昔比了.学习的必要性也越发强烈. autolayout也可以用代码实现,apple可爱的工程师开发了一个可视化语言VFL(Visual Format Language),这里不解释VFL的语法,但是要解释一个约束的类方法,个人觉得这对理解a

总结iOS 8和Xcode 6的各种坑

总结iOS 8和Xcode 6的各种坑 项目路径坑 模拟器的路径从之前的 ~/Library/Application Support/iPhone Simulator 移动到了 ~/Library/Developer/CoreSimulator/Devices/ 这相当的坑爹,之前运行用哪个模拟器直接选择这个模拟器文件夹进去就能找到项目 现在可好,Devices目录下没有标明模拟器的版本,图片上选中的对应的可能是iPhone 5s 7.1的 然后图片上的文件夹对应的应该是 iPhone 4s 7