如何对使用了autolayout的UIView添加动画

原文

hi,all:

在经过了一番犹豫之后,我决定将我自己做的这个小APP的源码公布给大家:

其出发点是和大家一起学习iOS开发,仅供学习参考之用。之前代码是托管与gitlab

上的,今天我将其pull到github上来了,大家可以自行下载:git clone git@github.com:lihux/twentyThousandTomatoes.git没有安装git或者不会用的童鞋,

请猛戳github地址:https://github.com/lihux/twentyThousandTomatoes,进去之后选

择download zip下载即可。

 

当我们对一个UIView使用了autolayout自动布局之后,也就意味着我们放弃了

传统的通过设置view的frame等方式手动的修改、确定这个view的位置、尺寸属性

甚至从某种程度上讲,我们应该忘记view的frame属性:它的确定不再取决于我

(手动的直接修改),而是通过我们在storyboard或者code中提供的约束条件

(constraints),通过一个自动布局引擎(苹果为autolayout采用的是Cassowary

布局引擎,参考文档:点击打开链接),算出这个view的frame。因此我们可以

认为使用了autolayout的view的frame属性是一个只的属性。在代码里认为的改动

这个view的frame并不能对这个view的frame产生真正的效果(事实也确实如此)。

      现在问题就来了,在以前我们经常通过对一个view的frame的修改产生view移动

的动画效果,那么在使用了autolayout的view世界中我们该如何实现相同的效果呢?

答案是,我们“将计就计”,通过改变这个view上的某个约束constraint然后在uiview的

animation block中触发layout来实现。

一、预期效果

        下面我们以一个简单的例子来进行详细的说明:

        如上图所示,整个界面都使用了autolayout,现在我们想实现这样一个效果:

我们点击显示生日的按钮的时候,整个view向上滑动,同时向上推出一个日期

选取器(date picker),类似于点击textfield,弹出键盘后整个界面为了避免被遮

住而向上移动的效果。选取完成日期后点击生日日期按钮或者完成按钮整个view向

下缩回,同时date picker向下滑出可视范围。

二、实现细节

        首先来看一眼storyboard中view的层级结构:如下图所示,从图中我们可以

看到,整个view的布局相当简单,就两级:根view和我们的date picker view,其

中date picker view包含了一个完成按钮和系统的date picker。这样的话,要实现

整个view和date picker view同时上移的效果,我们只需要对根view和date picker

 view同时做动画即可。


        考虑如何实现根view的动画效果,这里我们可以巧妙的通过修改根view的

bounds属性来实现根view的上移效果。注意这里我们需要明白view的bounds属性

和frame属性的区别,前者是相对于当前view的本地坐标系而言的,而后者则是相

对于当前view的父view的坐标系而言的。

        简单的讲,frame决定了一个view相对于父view的position和size信息。而

bounds则决定了当前view展示的内容相对于本地坐标系的位置。这里我们将view

身的可视内容和subviews可以看做一页纸上的内容信息,而view本身可以看成

是一枚放于纸上的放大镜,放大镜的大小不一定是和纸(content size)相同大小

的。bounds属性的作用就是确定这枚放大镜相对于纸的位置:一个bounds =

(0, 200, 300, 300)就意味着我们要将这枚放大镜向纸的下方移动200个points,但

放大镜相对于父view的位置仍是保持不变的,这样给我们的效就是这个view(显

示的内容)向上移动了200个points.


        改动bounds的origin属性并不会改动这个view的frame,通过这种展示内容的

移动给我们产生一种view向上移动了的幻觉。如上图中,“哪个位置...”为成为我们

放大镜中看到的第一行。

        根view上移动画的效果解决了,下面我们再来看日期选取器date picker,在

storyboard中对其增加的约束如下:定高207、trailing/leading/top相对于super 

view (根view)的位置。


        确定date picker view y轴方向上下移动的约束显然是top约束,点开top约束,

可以看到该约束的详细内容:

        一个约束可以描述为:firstItem.attributeA = secondItem.attributeB * multipler 

constant。

结合上图我们可以得出date picker view的top约束为

datePickerView.Top = topLayoutGuide.bottom * 1 + 400

我们可以通过修改这里的constant值来修改这个top约束以达到预期效果,事实

上通过修改而不是删除旧的constraint再添加新的constraint也正是苹果所推荐的,

NSLayoutConstraint.h头文件中有如下说明:


        这样,date picker view的上下移动就可以通过获取并修改其top约束来实现。

需要注意的是在代码中获取datepicker view的top约束实际上是要在其父view的

constraints数组中查找,这是因为每个view的constraints数组中保存的实际上是

layout 子view所需的约束的集合。

我们还要定义个辅助BOOL变量,已判断date picker view是否以弹出:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">@property (nonatomic, assign) BOOL hasShowPickerView;</span>  

       接下来定义一个辅助函数,用于查找date picker view的top约束并修改其

constant属性为给定的值:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void)replacePickerContainerViewTopConstraintWithConstant:(CGFloat)constant  
  2. {  
  3.     for (NSLayoutConstraint *constraint in self.pickerContainerView.superview.constraints) {  
  4.         if (constraint.firstItem == self.pickerContainerView && constraint.firstAttribute == NSLayoutAttributeTop) {  
  5.             constraint.constant = constant;  
  6.         }  
  7.     }  
  8. }  


        代码里我们在picker container view (即文中的date picker view)的

superview的constraints属性中查找,如果发现firstItem和firstAttribute属性分别是

date picker view和top,则该constraint即为目标约束,然后修改其constant属性。

在view首次被加载的时候我们想确保date picker view 处于整个view的最底部即隐

藏的状态,因而我们在viewcontroller的viewDidLoad方法中调用辅助方法修改一下

date picker view的top约束:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">[self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];</span>  

        在首次点击birthday button的时候动画修改根view的bounds和date picker 

view的top constraint,注意上移gap的计算。再次点击birthday button的时候将根

view的bounds恢复到正常值,date picker view的top constraint也恢复到viewDidLoad

中设置的值:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">- (IBAction)didTapOnBirthdayButton:(id)sender  
  2. {  
  3.     self.hasShowPickerView = !self.hasShowPickerView;  
  4.     if (self.hasShowPickerView) {  
  5.         CGRect birthdayButtonFrame = self.birthdayButton.frame;  
  6.         birthdayButtonFrame = [self.view convertRect:birthdayButtonFrame fromView:self.birthdayButton.superview];  
  7.         CGFloat birthdayButtonYOffset = birthdayButtonFrame.origin.y + birthdayButtonFrame.size.height;  
  8.         CGFloat gap = birthdayButtonYOffset - (self.view.frame.size.height - self.pickerContainerView.frame.size.height);  
  9.         CGRect bounds = self.view.bounds;  
  10.         if (gap > 0) {  
  11.             bounds.origin.y = gap;  
  12.         } else {  
  13.             gap = 0;  
  14.         }  
  15.         [self replacePickerContainerViewTopConstraintWithConstant:birthdayButtonYOffset];  
  16.         [UIView animateWithDuration:0.25 animations:^{  
  17.             self.view.bounds = bounds;  
  18.             [self.view layoutIfNeeded];  
  19.         }];  
  20.     } else {  
  21.         [self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];  
  22.         CGRect bounds = self.view.bounds;  
  23.         bounds.origin.y = 0;  
  24.         [UIView animateWithDuration:0.25 animations:^{  
  25.             self.view.bounds = bounds;  
  26.             [self.view layoutIfNeeded];  
  27.         }];  
  28.     }  
  29. }  
  30. </span>  


        上述代码中的[self.view layoutIfNeed]去掉也是没问题的。可能比较费解的是

根view.bounds.origin.y的上移gap的计算以及top constraint的constant值的计算,

关键实在真正理解view的frame和bounds的意义。

        至此程序达到了预期的效果,下面的gif图展示了动画效果。

1.竖屏:



2.横屏:


三、小结

        在使用autolayout之前我们写程序控制界面的构成就好比是开一辆手动挡的汽

车,虽然频繁换挡(修改frame)很繁琐,却也很享受那种可以完全控制汽车档位的

自由感。使用了autolayout之后则一下子升级为了自动挡汽车,切换档位的活不再由

我们直接操作,而只能通过油门(constraints)的大小来间接的改变汽车的档位。在

自动挡汽车里,我们必须要放弃直接控制档位的想法,那是不可能的了,我们必须要

学会通过熟练掌握脚下的油门和刹车来控制车速!在习惯了自动挡之后,相信大家也

一样能够得心应手的做自己想做的事情。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值