AutoLayout的那些事儿

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/guiyangxueyuan/p/5567176.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457805.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker-compose下载慢_编写Docker Compose时要注意的五大常见错误

在构建容器化的应用时,开发人员往往需要某种方法来引导启动目标容器,以对其进行代码级别的测试。尽管业界有许多方法可以实现该目的,但Docker Compose是目前最受欢迎的一种方法。它能够让如下两个方面变得容易实现:指定在开发过程…

前端测试利器--Browser-Sync启动命令

使用browser-sync启动命令cmd切换到项目的根目录下**1.browser-sync start --server --files "css/*.css"----------**使用两个*检测所有的目录**转载于:https://blog.51cto.com/1888512/1862054

VMware实现Android x86 8.1 从安装到使用

VMware实现Android x86 8.1 从安装到使用 虚拟机--Android 安装 Android系统配置 安装软件 个性化设计 托坑指南 一些终端模拟器的指令 虚拟机–Android 发现现在安卓虚拟机已经到了8.1,我就试试能不能安装并正常使用。由于版本过新,网上也没有一些系统的…

frame越过另一个frame_拥抱swoole(三)之用php实现一个混合服务器

混合服务器,就是可以同时支持http,websocket,tcp等的服务器,用swoole就是这么简单,分分钟,就可以愉快地搞物联网开发了,啥都支持,我采用官方的例子,创建一个混合服务器&a…

Hibernate学习系列————注解一对多单向实例

2019独角兽企业重金招聘Python工程师标准>>> 开发环境:MysqlEclipse 一对多单向的列子原理:一个班级,多个学生,学生端为多的一端,他们拥有一个外键指向相同的班级。 项目结构 需要的jar包 hibernate.cfg.xm…

Spring学习笔记--自动装配Bean属性

Spring提供了四种类型的自动装配策略: byName – 把与Bean的属性具有相同名字(或者ID)的其他Bean自动装配到Bean的对应属性中。byType – 把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。constructor – 把与Bean的构造器入参具有相同类型的其他…

sudo apt-get nmap 报错锁占用

在Ubuntu中用apt-get命令安装软件是出现如下错误: 网上搜了一下原因,说是有另外一个程序在运行,导致锁不可用,原因可能是赏析运行更新或安装没有正常完成。这是因为上次更新或者安装没有正常完成。 网上的两种解决方法&#xff1…

python逐行读取txt写入excel_用python从符合一定格式的txt文档中逐行读取数据并按一定规则写入excel(openpyxl支持Excel 2007 .xlsx格式)...

前几天接到一个任务,从gerrit上通过ssh命令获取一些commit相关的数据到文本文档中,随后将这些数据存入Excel中。数据格式如下图所示观察上图可知,存在文本文档中的数据符合一定的格式,通过python读取、正则表达式处理并写入Excel文…

筋斗云newcloud错误码列表

响应码信息备注440Ip Error客户送IP错误441Callee Number Error被叫号码位数错误(标准11位正确,错误加前缀0,或其他前缀)442Called Operator Error被叫运营商错误(支持移动,不支持联通电信)443N…

Extjs 之 initComponent 和 constructor的区别(转)

在创建自定义类时,先构造(constructor)后初始化(initComponent)。如:(在旧的Extjs 版本中使用 Ext.extend 实现扩展) Ext.define(Btn,{ extend:Ext.button.Button, init…

hive遍历_从Hive中的stored as file_foramt看hive调优

一、行式数据库和列式数据库的对比1、存储比较行式数据库存储在hdfs上式按行进行存储的,一个block存储一或多行数据。而列式数据库在hdfs上则是按照列进行存储,一个block可能有一列或多列数据。2、压缩比较对于行式数据库,必然按行压缩&#…

oracle sql语句 从指定条数查询

现有表A 查询从第10行之后的数据 select a from ( select a, rownum r from A ) where r > 10 order by r; 实际工作中例子 select account,acct_name from ( select account, acct_name, rownum r from pmctl_nonsleep_acct ) where r > 10 order by

帮助孩子学会感恩_页数204_出版日期2015.03_完整版PDF电子书下载

帮助孩子学会感恩_页数204_出版日期2015.03_完整版PDF电子书下载 带索引书签目录高清版_13813212 下载链接http://pan.baidu.com/s/1geEmUeZ 【作 者】(英)蒂姆惠特尼(TimWhitney)著【丛书名】陪孩子成长系列丛书【形态项】 204 …

xwpftablecell设置字体样式_HTML的文字样式

font 属性可以用来作为 font-style, font-variant, font-weight, font-size, line-height 和 font-family 属性的简写,或将元素的字体设置为系统字体。字体修改font-family 属性:设置HTML页面中的字体font-size 属性:设置字体大小font-weight…

将中文标点符号替换成英文标点符号

/// 转全角的函数(SBC case) /// ///任意字符串 /// 全角字符串 /// ///全角空格为12288,半角空格为32 ///其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 ///public string ToSBC(string input) { //半角转全角:char[] cinput.ToCh…

Centos6.5升级GCC

由于CentOS自带的gcc实在是老掉牙了,所以决定升级一下gcc,下面介绍如何进行源码编译,升级gcc。 从GNU网站下载你想要的gcc版本,链接:ftp://ftp.gnu.org/gnu/gcc/,选择合适的gcc版本,然后下载&am…

oracle sql语句 exists

exists 这个关键字只是个查询条件 用来判断后面跟的查询语句是否查找到记录 查找到为真 反之为假 例子 select * from ammst_corp a where account 999999999999999999 and exists ( select 1 from pmrgt_unit where unit_code a.open_unit ) 查找 9999999999999999…

python金字塔_高斯金字塔与拉普拉斯金字塔的原理与python构建

高斯金字塔和拉普拉斯金字塔【1】在图像相关领域应用广泛,尤其是图像融合和图像分割方面。本文从理论和opencv实现两个方面对两种金字塔进行了介绍,并给出了二者的视觉效果。1、高斯金字塔在计算机视觉与图像处理相关任务中,经常需要使用同一…

mongodb在32位机的连接

Windows 32bit版本安装Mongodb时,会发生的下面问题 2016-05-09T00:09:45.1240800 I STORAGE [initandlisten] exception in initAndListen: 28663 Cannot start server. The default storage engine wiredTiger is not available with this build of mongod. Pleas…

oracle sql 语句 start with ...... connect by prior .......

这个查询条件可以理解为递归查询 select up_unit_code from pmctl_nuit START WITH unit_code 1188899Q CONNECT BY PRIOR up_unit_code unit_code 语句理解: 首先根据条件 START WITH unit_code 查询到 up_unit_code 显示 然后 CONNECT BY P…