您的位置:首页 > 移动开发 > Swift

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

2014-12-01 14:11 267 查看

        一、概述

        使用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版本:

<span style="font-size:18px;">- (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;
}</span>


        Swift版本:

<span style="font-size:18px;">    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
}
</span>

        代码里我们使用的苹果的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的一个利器!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: