知识共享许可协议本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。本文仅作为个人学习记录使用,欢迎在许可协议范围内转载或使用,请尊重版权并且保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用RSS方式订阅本站,这样您将能在第一时间获取本站信息。

start

冒个泡,体现下存在感,哈哈哈哈,最近发现iOS自带的动画效果总是困扰着我-,-比如,iOS7上返回时,会自动加一个半透明的层做渐变感,比如返回的时候,总是整个view一起动的,动画过程中,我需要隐藏和显示导航栏等这些要求,原生的动画总是无法满足这些小小的要求!(ノ`□´)ノ⌒┻┻

经过数小时上网找资料,发现大神们要不就是一笔带过,要不就是懒得讲很多,于是,发现还是自己写比较科学啊┓( ̄∇ ̄)┏,下面我们来尝试做一个

做的方法

制作方法还是按照古法炮制,继承一个NSNavigationController然后重写其中的pop和push方法以达到我们的目的,在我的理解,导航控件他的pop和push方法,实现了在他自己的控制器栈中实现不断的出栈和入栈的操作,然后播放一小段动画,愉悦下大家,那么我们要做的事情就是去掉原来的动画,做pop,push的动作,然后播放我们自己的动画

截图工具

做动画之前,必须要有的一个东西就是,我们要让什么东西来动呢,那导航上必然是两个ViewController中的View的内容喽,那么我们就需要对UIView进行扩充,实现获取View中的内容。

根据观察研究,发现有4个函数能够满足我们的需求,他们分别是:

  • - (void)renderInContext:(CGContextRef)ctx
  • - (BOOL)drawViewHierarchyInRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates
  • - (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates
  • - (UIView *)resizableSnapshotViewFromRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates withCapInsets:(UIEdgeInsets)capInsets

这两个方法都是UIView提供的方法,第二个以后的方法都是iOS7以后提供的方法,第一个方法,由于对于毛玻璃等高端酷炫的效果支持不太好(容易出现黑块)并且我们都面向iOS7吧,所以就不用了。后面两个snapshot的方法,会根据当前的视图创建一个snapshot的视图(静态的,啥也不能操作)但是这两个方法没办法处理正在进行中的动画和比较复杂的效果(其实就是毛玻璃啦)。

所以,我就用drawViewHierarchyInRect:afterScreenUpdates这个方法喽。

嘛,为了各种模块化啊,方便啊,之类的,我们就做一个category啦,就像下面的样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@implementation UIView (Snapshot)

- (UIImage *)snapshot
{
CGSize size = self.size;
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
[self drawViewHierarchyInRect:self.frame afterScreenUpdates:YES];
UIImage *snap = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGSize size2 = self.size;
// 看这里,看这里
if (fabs(size.height - size2.height) > 0.0001 || fabs(size.width - size2.width) > 0.0001) {
UIGraphicsBeginImageContextWithOptions(size2, NO, [UIScreen mainScreen].scale);
[self drawViewHierarchyInRect:self.frame afterScreenUpdates:YES];
snap = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}

return snap;
}

@end

上面『看这里,看这里』后面的那一段大家会觉得比较奇怪吧,为啥要多截取一次呢。。。

原因是,发现如果view还未显示,也就是将要push的view,他的size是错误的,只有在完成了drawViewHierarchyInRect:afterScreenUpdates方法调用后,才会得到正确的大小,如果用错误的大小渲染的话,得到的结果往往是拉伸的截图。所以在这里,截取第一次以后,发现view的size发生变化的话,就重新再截取一次。

尝试过调用viewDidLoadlayoutSubviewsneedLayoutSubviews之类的方法,但是都没有效果,所以这里只是一种替代的方法了,如果大家有更好的方法,也可以告诉我怎么来做。

视图的切换

OK既然截图的工具有了,那么下一步就是完成视图切换的动作了,我们要做的是,截图->切换->截图->放动画,那么顺着来吧

实现切换

下面的内容将把pushViewController:animated:方法完全拆开,一步一步告诉大家怎么来做

第一步,如果这个导航操作本来就不需要动画,那就直接跳过了,这一步push和pop的行为都是一样的

if (!animated) {
    [super pushViewController:viewController animated:animated];
    return;
}

接下来,我们在切换之前需要对当前的视图进行截图,用来做动画的素材

UIViewController *currentVC = self.topViewController;
UIImage *topImage = [currentVC.view snapshot];

完成了之后,我们就悄悄的完成我们的push操作,为啥是悄悄的呢-,-因为我们是不用动画的push

[super pushViewController:viewController animated:NO];

这样就完成了push的动作,完成push的动作后,下一步自然是截取新的截图

UIImage *nextImage = [self.topViewController.view snapshot];
self.topViewController.view.hidden = YES;

上面hidden刚push的view是非常必要的,因为我们在调用superpushViewController:animated:方法后,新的view可以说已经是在屏幕显示的时候就绘制好了,这样他还一直显示着的话,会在我们的动画背景下面显示个他自己,所以这里我先把它隐藏了,等动画放完了,再显示

完成了动画素材的准备,接下来,我们就放动画愉悦下观众们了,这里我的动画就非常简单粗糙了,就准备左右移动下啦,哈哈哈哈,毕竟我写的动画太丑太low了┓( ̄∇ ̄)┏以及我的动画过程也很丑,这里是大家发挥自己想象力的地方了

UIImageView *topImageView = [[UIImageView alloc] initWithImage:topImage];
UIImageView *nextImageView = [[UIImageView alloc] initWithImage:nextImage];
[self.view addSubview:topImageView];
[self.view addSubview:nextImageView];
CGRect frame = nextImageView.frame;
frame.origin.x += frame.size.width;
nextImageView.frame = frame;

CGRect frameTopPos2 = topImageView.frame;
frameTopPos2.origin.x -= frame.size.width;

CGRect frameNextPos2 = nextImageView.frame;
frameNextPos2.origin.x = 0.0f;

[UIView animateWithDuration:2 animations:^{
    topImageView.frame = frameTopPos2;
    nextImageView.frame = frameNextPos2;
} completion:^(BOOL finished) {
    self.topViewController.view.hidden = NO;
    [topImageView removeFromSuperview];
    [nextImageView removeFromSuperview];
}];

嗯嗯,这丑到爆的动画就完成了,希望大家多画点花在自己的切换动画上ヾ(´▽`; )ノ

pop的方法也比较类似这里就不浪费大家时间了,只是需要注意的是,pop出来的ViewController需要作为pop方法的返回值

end

有始有终,发现好久没写了,刚好把最近开发遇到的种种奇葩和悲剧的替代方案都拿出来讲讲了,画图那个-,-慢慢写喽,就酱

最后欢迎大家订阅我的微信公众号 Little Code

公众号

  • 公众号主要发一些开发相关的技术文章
  • 谈谈自己对技术的理解,经验
  • 也许会谈谈人生的感悟
  • 本人不是很高产,但是力求保证质量和原创