自动布局autolayout使用总结(源码含swift版本)

一、概述

使用autolayout有一段时间了,Objective-C和swift下、iOS7和iOS8下都在用,

一路遇到了不少的坑,随遇随填,到今天也算是积累了不少经验了,这里总结一下,

通过自己新建的一个Doubi Demo来分享给大家。Doubi Demo我已上传到github上

去了(地址是:https://github.com/lihux/iLihuxAutoLayout),以后每篇文章的demo都

将放在github上,供大家参考。源码的workspace中有两个工程,分别使用OC和swift

实现一个相同的功能,大家在学习autolayout的同时也能够通过两种语言的对比来学习

、体会OC和swift编程的差异,以后的Demo也尽量同时用两种语言实现。

在学习英语的时候,老师会告诉我们,真正能够熟练应用英语的标志是You

naturally not only speak in English but think in English,即要学会用英语去思考,而

不仅仅是会将母语思考后的句子在大脑里翻译之后用英语说出来,能够真正做到这一

点,才算真正的掌握了英语。同样的道理,要真正掌握autolayout,就必须忘掉曾经

的frame,学会用autolayout的语言来思考和实践。具体一点儿来说,在创建UI的时候,

在storyboard或者代码中使用autolayout来构建UI而不是直接修改frame;在实现动画

的时候,也同样的通过修改autolayout来实现动画效果。

一般而言,自动布局(autolayout)的使用分为两个步骤:首先是创建autolayout,

这一步,主要是通过代码和storyboard相结合的方式,通过对UI空间(UIView、

UIbutton)等添加约束(constraint),让UI满足我们的产品的静态设计需求;其次

就是根据用户交互来修改或者增删已有UI上的约束,以产生相应的动画效果。

约束的增删改可以通过storyboard添加和代码添加两种方式,前者的优势就是

能够很直观的迅速添加约束,80%-90%的约束都能够通过这种方式迅捷的完成,实

践证明这种方法效率并不比以前的frame+autoresizingmask的方式低;后者的优点

则是动态、灵活,可以在运行时根据需要新建约束,满足特定环境下的需求,而修

改、删除已有约束,通常也只能通过代码来实现。总体而言,会有10%~20%的场景

会用到代码添加约束。这两种方式是互补的,要想游刃有余的使用自动布局为自己

工作,就必须同时掌握这两种方法。

二.详解

下面通过一个实际的Doubi Demo来展示一个自动布局的一般过程,该场景包括:

1)在storyboard中创建约束;

2)通过修改约束来实现简单的动画;

3)特定的场景必须要通过手动代码添加约束;

Demo分为两个场景,场景一如下图所示:

场景1:主要展示storyboard中添加约束以及通过约束实现动画效果

场景2:必须要使用代码添加约束的场景

1)在storyboard中创建约束

在我之前的博文中已经详细讲过如何在sb中创建约束,这里不再赘述,只简单

的列出我所添加的约束,如下图所示:

这里创建约束需要提及的有两点:其一,我们引入了一个辅助的view,如我

前文中讲过,这也是自动布局中常用的技巧之一。通过一个anchor view的引入,

我们能够巧妙的将相对于全局的布局工作转化为相对于局部(anchor view)的布

局工作,一定程度上降低工作量和布局的复杂度。让它恰好位于整个view的正中心

(水平居中+垂直居中)作为我们的“锚点”,然后所有其他的view都(直接或间接

的)以这个辅助view作为参照物来添加约束,这样就能保证整个UI在横屏或者小屏、

手机上也能有一个很好的展示效果。

横屏效果

其二是,我们对Doubi同时在水平方向上施加了leading和trailing两种互斥约

,这个主要用于后面动画的实现,为了让两个相互冲突的约束能够正常存在,我

们必须要将其中一个约束的优先级调低(默认优先级是1000,这里我们调低至750,

其实是只要比1000低就好)。

2)通过修改约束实现简单的动画

上面我们讲到对于Doubi我们添加了两个互斥的约束,意在实现点击左右两个

按钮时Doubi能够向左和向右滑动:修改Doubi的约束然后调用UIView的animation

block实现动画。

要想修改Doubi身上的约束(constraint)首先就是要能够获取和修改作用于Doubi

上的约束,我之前的一篇博文讲过一种方法,通过遍历其superview上的constraints

数组通过constraint属性的判断来找到这个约束(和父view之间的约束都只放在父view

的constraints属性里而不是自己的constraints属性中)。其实Doubi Demo使用了一

个更好的方法:通过IBOutlet的方式直接将storyboard中的约束引入到代码里:

使用IBOutlet可以直接将约束从storyboard中直接拖到代码里

这里需要注意的时,我们使用了strong属性修饰印出来的约束,这样会防止在

约束不被使用的时候依旧保持一个强应用而不被释放,强引用在OC是用strong关键

字,而在swift里则不用weak修饰的就表示是strong。

下面是OC版本的动画代码:

- (void)animatedMoveDoubiIsLeft:(BOOL)isLeft
{
    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) { //ios8
        self.kidLeftCenterConstraint.active = isLeft;
        self.kidRightCenterConstraint.active = !isLeft;
        [UIView animateWithDuration:kAnimationDuration animations:^{
            [self.view layoutIfNeeded];
        }];
    } else { //ios7
        NSLayoutConstraint *constraintToRemove = !isLeft ? self.kidLeftCenterConstraint : self.kidRightCenterConstraint;
        NSLayoutConstraint *constriaintToUse = isLeft ? self.kidLeftCenterConstraint : self.kidRightCenterConstraint;
        [self.douBi.superview removeConstraint:constraintToRemove];
        [self.douBi.superview removeConstraint:constriaintToUse];
        [self.douBi.superview addConstraint:constriaintToUse];
        [UIView animateWithDuration:kAnimationDuration animations:^{
            [self.view layoutIfNeeded];
        }];
    }
}

在swift中是酱紫的:

    func animatedMoveDoubi(isLeft: Bool)
    {
        if NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1 {
            self.kidLeftCenterConstrait.active = isLeft
            self.kidRightCenterConstrait.active = !isLeft
            UIView.animateWithDuration(kAnimationDuration, animations: { () -> Void in
                self.view.layoutIfNeeded()
            })
        } else {
            let constraintToRemove = isLeft ? self.kidRightCenterConstrait : self.kidLeftCenterConstrait
            let constriaintToUse = isLeft ? self.kidLeftCenterConstrait : self.kidRightCenterConstrait
            self.douBi.superview!.removeConstraint(constraintToRemove)
            self.douBi.superview!.removeConstraint(constriaintToUse)
            self.douBi.superview!.addConstraint(constriaintToUse)
            UIView.animateWithDuration(kAnimationDuration, animations: { () -> Void in
                self.view.layoutIfNeeded()
            })
        }
    }

需要说明的是:constraint中的active用来标识当前约束在它所在的view使用自

动布局的时候是否被应用,类似UIButton中的enable属性,但是这个属性只有在最新

的iOS8中才被引入,至此如果APP要支持iOS7的设备,则不能使用该属性,而只能

通过删除相关约束来实现。有些童鞋灵机一动也许会说:对了,我们可以修改那俩

互斥的约束的优先级来实现选取其中一个约束来实现动画嘛。实际情况是:不行!

一旦一个约束被建立起来,你能改变的其实很少:一般也只有consant和active属性,

其他属性一旦添加到view之后便不能修改,否则对程序会有未知的后果。建议使用

autolayout的童鞋好好研读一下NSLayoutConstraint.h文件和其参考文档,了解一下

constraint中有哪些成员变量和方法,都有什么作用。

3)特定的应用场景下使用代码添加约束

在Doubi Demo的场景2中,我们创建了一个tableview,运行起来有三个cell,

用户点击cell上的删除图标删除cell,当cell都背删完了的时候,我们希望tableview

展示一个提示内容为空的view出来,这在一般的需要联网的APP中相当常见:当

断网的时候无法获得数据,给用户展示一个view用于提示无网络。这一般都是通过

设置tableview的background view来实现的,我们把一个uiview设置好提示图标,然后

将其赋值给tableview的backgroundView属性即可,之后在返回cell个数的回调方法

中如果cell个数为0就显示empty view,否则隐藏。

在实现上,我们现在storyboard将这个empty view布局好,通过IBOutlet引入

代码中,在viewDidLoad的方法中将其赋值给tableview的backgroundView属性,

注意,在iOS8上是可以直接将empty view赋值给tableView.backgroundView,empty

view和其superview之间添加约束或者不再添加约束都没问题了,但在iOS7中则不

行:如果直接将empty view 直接赋值过去,不管添不添加约束都会奔溃!苹果真是

坑坑不息啊!解决方法是再创建一个不初始化任何约束和frame信息的空view作为

empty view 的父view,将该view赋值给background view,建立好empty view和这个

container view之间的约束关系即可,代码如下所示:

Objective-C版本:

- (void)customUI
{
    UIView *backgroundView = [UIView new];
    [backgroundView addSubview:self.emptyView];
    NSDictionary *views = @{@"backgroundView": self.emptyView};
    [backgroundView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[backgroundView]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views]];
    [backgroundView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[backgroundView]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views]];
    self.tableView.backgroundView = backgroundView;
}

Swift版本:

    func customUI()
    {
        let backgroundView = UIView()
        backgroundView.addSubview(self.emptyView)
        let views = ["backgroundView": self.emptyView]
        self.emptyView.superview?.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[backgroundView]|", options: NSLayoutFormatOptions.allZeros, metrics: nil, views: views))
        self.emptyView.superview?.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[backgroundView]|", options: NSLayoutFormatOptions.allZeros, metrics: nil, views: views))

        self.tableView.backgroundView = backgroundView
    }

代码里我们使用的苹果的VFL语言实现约束,实际上代码创建约束还可以通过

NSLayoutConstraint的constraintWithItem:relatedBy:toItem:attribute:multipler:constant

方法实现,后者稍显麻烦,但是若果要建立放缩因子不为1(multipler !=1)的约束

时就只能通过这种方法实现,VFL语言无法修改放缩因子,另外使用VFL方法创建

出来的是一个约束数组,而后者则创建了一个单独的约束对象,关于这些细节,这

个大家心里要有数。VFL语言格式虽然咋一看上去有些诡异,但其实比较形象、简

单的,如果你认真弄明白了,用起来是相当爽的,分分钟添加一个约束不是什么问

题。研习VFL语言我这里给大家推荐一篇老外写的博文(不过好像被墙了,好吧,

抽空我给翻译过来,除了看苹果官方文档,这一遍文章足矣。):

http://commandshift.co.uk/blog/2013/01/31/visual-format-language-for-autolayout/

三、总结

本文主要从比较粗的范围、依托一个小小的Doubi Demo,总结性的介绍了

autolayout的使用和其中的一些技巧和坑坑。细节部分不再详述,随便一搜,网上

一堆,我就不再重复造轮子了。自动布局的使用在使用了storyboard的项目中,80%

以上的工作都是在storyboard中很轻松的完成(倘若你的项目还没有使用storyboard

(故事版),那么好吧,autolayout使用起来就有些费劲了:你会有很大段很大段的

代码要写,即使是用VFL语言来写,其可读性也远远比不上在storyboard中图形化来

的直观方便)。剩下一些必须要代码实现的部分,诸如通过修改约束实现动画以及一

些特殊只能手动添加约束的场景。读别人文百边,比如自己实践,现在就开始写自己

的布局代码吧,唯有实践是学习的最佳途径!

最后的最后,总结并罗列一些autolayout使用的过程中的经验:

1.一定要熟练的使用storyboard,熟练的掌握如何在storyboard中添加约束,因为这

一块儿占据了80%以上的布局量(什么?Storyboard你还不会用?那赶紧学吧,不要跟我说纯代码写的程序才叫NB,现实是,目前的技术成熟度和产品开发效率的需求都要求你快快使用起storyboard来,况且纯代码里写约束实在是一个费时费力还不讨好的事情,百分百的鸡肋);

2.动画的实现,可以借助IBOutlet来方便的修改在sb中创建的约束来实现动画;

3.学会使用VFL语言,它是你代码实现constraint的一个利器!

时间: 2024-11-08 11:05:08

自动布局autolayout使用总结(源码含swift版本)的相关文章

MultiThread(VS2013 MFC多线程-含源码-含个人逐步实现文档)

原文:http://download.csdn.net/download/jobfind/9559162 MultiThread(VS2013 MFC多线程-含源码-含个人逐步实现文档).rar

真正shopex分销王2代DRP系统源码正版安装版本终身商业授权

真正ShopEx分销王系统2代正版授权.该商业程序已经完整授权,已测试100%完整能用.很多朋友来问是否免费版的源码?错,这是和官方一样的平台版本,100%无限制功能使用,跟官方付费使用的授权版一样.全部功能注册没有任何限制,能完美使用分销功能,开展下级的代销平台,多用户注册,供货,同步淘宝.抓抓等所有功能.本人可是在淘宝花了几千多块才购得此真正的源码版本,因现实工作繁忙.现在不需要此套分销程序源码,故忍痛割爱想转让给需要的所有电子商务同行,仅售600元,谢绝还价,已经亏本让利了~一口价交易,如

centos 源码升级g++版本

1.用GNU上下载对应版本的源码包到本地,官网:http://ftp.gnu.org/gnu/gcc/ 这里选用最新的包: wget http://ftp.gnu.org/gnu/gcc/gcc-7.1.0/gcc-7.1.0.tar.gz 2.解压对应的包,然后进入到gcc-7.1.0目录下: tar zxvf gcc-7.1.0.tar.gz 3.下载更新所需要的依赖包,执行: ./contrib/download_prerequisites 下载完成后,会看到新生成的文件 -rw-r--r

java画图程序_图片用字母画出来_源码发布_版本二

在上一个版本:java画图程序_图片用字母画出来_源码发布 基础上,增加了图片同比例缩放,使得大像素图片可以很好地显示画在Notepad++中. 项目结构: 运行效果1: 原图:http://images.cnblogs.com/cnblogs_com/hongten/356471/o_imagehandler_result1.png 运行效果2: 原图:http://images.cnblogs.com/cnblogs_com/hongten/356471/o_imagehandler_res

centos 7.4 源码安装最新版本的lamp架构及搭建phpMyadmin

所需的压缩包,如下图: 1.安装apache服务[[email protected] ~]# tar xf apr-1.6.2.tar.gz -C /opt/ //apache插件[[email protected] ~]# tar xf apr-util-1.6.0.tar.gz -C /opt/[[email protected] ~]# yum install -y bzip2 //.tar.gz格式是默认的压缩文件格式[[email protected] ~]# tar xf httpd

Dubbo常用配置及源码解析-多版本支持

1.多版本的支持 如何发布服务,需要将需要暴露的服务接口发布出去供客户端调用,需要在java同级目录新建一个resources目录,然后将resoureces目录标记成Test Resoureces Root,然后在esources目录下新建MATE-INF.spring目录,在该目录下添加配置文件dubbo-server.xml文件 dubbo的服务端配置文件如下 <?xml version="1.0" encoding="UTF-8"?> <b

源码部署gitlab版本控制系统

本文章适用于需要自建gitlab代码管理系统环境 一. 部署前环境准备 1.安装软件包及版本要求 1. Ubuntu/Debian/CentOS/RHEL 2. ruby 2.4+ 3. git 2.7.2+ 4. go 1.0.0+ 5. redis 3.2+ 6. node 8.0+ 7. MySQL(5.6+) or PostgreSQ (9.4+) 原文地址:http://blog.51cto.com/blief/2296409

socketserver源码分析udp版本

request对于tcp是conn,对于udp是(data,self.socket)data是不包含地址的消息,self.socket是套接字 ThreadingTCPServer()——>绑定接听,生成一个套接字对象,封装了一些属性 ThreadingUDPServer()——>生成套接字对象,并绑定端口 serve_forever——>tcp:循环等待建立链接,再实例化,循环收发消息:udp:循环收消息,再实例化,循环发消息 有套接字对象就可以收发消息 原文地址:https://ww

读 SnapKit 和 Masonry 自动布局框架源码(轉)

前言 一直觉得 SnapKit 和 Masonry 这两个框架设计和封装的很好,用起来的体验也是一致的,翻了下它们的源码,对其设计方式和涉及的技术做了下记录.文章打算围绕,给谁做约束?如何设置约束?设置完后如何处理?这三个问题看看 SnapKit 和 Masnory 分别是怎么做的,正好也能够窥探下作者是如何利用 Swift 和 Objective-C 两个不同语言的不同特性做到一致的使用体验的. 如果还不了解这两个框架的使用的话可以参看它们项目 GitHub 说明:GitHub - SnapK