首页 > 代码库 > 自动布局autolayout使用总结(源码含swift版本)

自动布局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的一个利器!

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