iOS present一个viewController后动画失效的问题

问题描述

今天遇到一个基本问题,那就是:假设A为rootViewController,在适当的时机使用A present 另一个viewController B。然后会发现写在B viewDidLoad中的动画不会执行。

解决办法

  • 方法一:将动画写在viewWillAppear或者之后执行的方法中,但是需要注意该方法会多次执行。
  • 方法二:将动画的removedOnCompletion属性设置为NO。

类似问题(切换后台导致动画失效)

在点击home键切换到后台,然后在切换回来时,会发现动画都无效了。解决办法与上面的类似:

  • 方法一:设置观察者在app将要进入前台时重置动画。
  • 方法二:将动画的removedOnCompletion属性设置为NO。

总结

相信在开发中大多数人会遇到描述的第二个问题;而遇到present后动画失效这个问题的人应该要少一些,因此搜索引擎上也就十分罕见,所以我把它记录在此。至此问题就解决了,下面将要写的是我是如何定位到这个问题的,如果您的时间宝贵,那么可以略过下面的篇幅。


实际遇到的问题和排查过程

问题描述

上面描述到的问题是我最后简略所总结出的。实际的问题是:同事在一个view的初始化过程中添加了一个动画(CoreAnimation),而这个动画不执行了。

分析过程

  • 这个时候大家的第一反应肯定是动画是不是写的有问题?于是我在原来加动画的地方写出了另一个动画(确定正确的)然后 command + R 发现然并卵。ps:同事也是试验过其他动画的。
  • 接着当然就是短暂的懵逼咯,仔细回想是不是忘记了什么,毕竟很久没有写动画相关的代码了,但是怎么想都想不出是为什么。
  • 接下来想到是不是viewController哪里出了问题,于是找到了viewController的实现代码看了看,嗯,没什么问题。又看了初始化的时候,简单的alloc、init似乎也没什么问题。
  • 那么问题出在哪里呢?然后我注意到了这个viewController是present出去的,难道说是这个原因?恰巧这是一个可以Push的视图控制器(拥有navigationController),于是改用push方法,发现添加的动画可以执行了!
  • 但是我还是不知道到底是为什么present的vc不执行动画啊,接下来我直接将一个新创建的viewController(无多余的代码),设置为rootViewController,在这个rootViewController中present出上面描述的含有动画的视图控制器,发现动画还是不执行。
  • 我又重新创建了一个视图控制器,在viewDidLoad中加入了相关的动画,然后用rootViewController present它,接着运行工程,发现还是不执行。
  • 为了彻底排除是工程中一些配置所引起的原因,只有重新起一个工程来测试,然而在新的工程中还是不执行,那么至此可以断定,是present导致的动画不执行。
  • 问题找出来了,那么为什么?突然我想到,以前在做一个持续性动画的时候,发现切换到后台在切换回来后动画就不起作用了,最后经过搜索和验证发现是切换到后台时所有的动画被移除了,那么会不会present有类似的手法移除了动画呢?那怎么验证? 简单的方式就是用dispatch_after。在设置延迟之后,发现动画执行了。下面是代码验证过程:
 - (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];
    
    v = [[UIView alloc] initWithFrame:CGRectMake(0, 20, 100, 100)];
    v.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:v];
    
    CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    ani.fromValue = @0;
    ani.delegate = self;
    ani.toValue = @M_PI;
    ani.duration = 2.0;
    ani.repeatCount = HUGE_VALF;
    [v.layer addAnimation:ani forKey:@"opm"];
    NSLog(@"%@",v);

    
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s  %@",__func__,v);
}

上述代码中,在添加了动画之后打印了一次v,然后viewWillAppear中也打印了一次v(目的是看看有没有动画在其中),控制台的输出为:

del07-10[8293:207637] <UIView: 0x7f836fc0b710; frame = (0 20; 100 100); animations = { opm=<CABasicAnimation: 0x6000000340e0>; }; layer = <CALayer: 0x600000034240>>
del07-10[8293:207637] -[PViewController viewWillAppear:]  <UIView: 0x7f836fc0b710; frame = (0 20; 100 100); layer = <CALayer: 0x600000034240>>

可以清楚的看到,在不加延迟时动画在viewWillAppear中就已经不存在了。
当为加动画的代码块设置延迟后,动画就正常执行了。

  • 我就有点好奇,他是用什么方法移除所有的动画的,不会是用layer的removeAllAnimations方法吧?虽然自己都不相信苹果会用会用这个方法,但是好奇心太强,想试试,我想到两个试的办法:
    一、写一个Layer继承自CALayer,然后把相关layer都换成这个类的。
    二、Method Swizzling,利用runtime交换removeAllAnimations的实现。
    自然而然地,我采用了第二个方法,因为它有逼格啊 O(∩_∩)O
    为了保证一开始方法就被替换,我在appDelegate中加入了如下代码:
AppDelegate.m
- (void)exchangeMethod {
    Method original = class_getInstanceMethod([CALayer class], @selector(removeAllAnimations));
    Method custom = class_getInstanceMethod([AppDelegate class], @selector(customMethod));
    method_exchangeImplementations(original, custom);
}

- (void)customMethod {
    NSLog(@"%s开始",__func__);
    NSLog(@"self=%@",self);
    NSLog(@"%s结束",__func__);
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [self exchangeMethod];
    return YES;
}

经过以上代码,我期待,在present的时候会打印出出我想要的东西,事实就是并没有打印,既然一开始就料到了苹果不会采用这么“低端”的方法来移除动画,所以也没什么好伤心的。写都写了,不如试试切换后台呢?
我把Present 到的那个viewController做了下如修改(经过一段延迟之后再加动画),使动画能够运行:

//关键代码
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        v = [[UIView alloc] initWithFrame:CGRectMake(0, 20, 100, 100)];
        v.backgroundColor = [UIColor orangeColor];
        [self.view addSubview:v];
        
        CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
        ani.fromValue = @0;
        ani.delegate = self;
        ani.toValue = @M_PI;
        ani.duration = 2.0;
        ani.repeatCount = HUGE_VALF;
        [v.layer addAnimation:ani forKey:@"opm"];
    });

等待动画执行后,我把app切换到后台,控制台输出:

del07-10[9174:251348] -[AppDelegate customMethod]开始
del07-10[9174:251348] self=<CALayer: 0x600000031280>
del07-10[9174:251348] -[AppDelegate customMethod]结束

很令人激动啊,居然执行了,也就是说调用了removeAllAnimations方法,如果有童鞋不理解为什么这里输出的self是CALayer而不是Appdelegate,那么你们理解runtime的消息发送机制后应当会理解,这里就不多说了。

  • 既然证明调用了,那么就可以说,切换后台和prensent他们清楚动画的方式不一致。
    仔细推敲会发现,上面那句话是错的,因为此时此刻我们并不知道这个Layer是属于谁的。然后我尝试打印出可见view以及superview中的layer,想要看看是哪个layer调用了移除动画的方法。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        v = [[UIView alloc] initWithFrame:CGRectMake(0, 20, 100, 100)];
        v.backgroundColor = [UIColor orangeColor];
        [self.view addSubview:v];
        
        CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
        ani.fromValue = @0;
        ani.delegate = self;
        ani.toValue = @M_PI;
        ani.duration = 2.0;
        ani.repeatCount = HUGE_VALF;
        [v.layer addAnimation:ani forKey:@"opm"];
        
        UIView *x = v;
        while (x) {
            NSLog(@"Layer寻找:%@",x.layer);
            x = x.superview;
        }
    });

重新运行,等待动画执行,切到后台。控制台输出如下:

del07-10[9362:256937] Layer寻找:<CALayer: 0x60000003ac80>
del07-10[9362:256937] Layer寻找:<CALayer: 0x60800003d240>
del07-10[9362:256937] Layer寻找:<CALayer: 0x60800003fde0>
del07-10[9362:256937] Layer寻找:<UIWindowLayer: 0x60800003cb80>
del07-10[9362:256937] -[AppDelegate customMethod]开始
del07-10[9362:256937] self=<CALayer: 0x60800022d240>
del07-10[9362:256937] -[AppDelegate customMethod]结束

经过多次的试验,发现没能找到与调用removeAllAnimations layer相同的layer。很失望啊!
然后各种折腾,最后静下心来,在断点调试中看到了如下信息:

<CALayer:0x60000003ae40; position = CGPoint (0 0); bounds = CGRect (0 0; 0 0); delegate = <UIKeyboardImpl: 0x7f84686238e0; frame = (0 0; 0 0); layer = <CALayer: 0x60000003ae40>>; opaque = YES; allowsGroupOpacity = YES; >

也就是说这个调用removeAllAnimations的layer可能是和键盘的动画有关,虽然我不知道UIKeyboardImpl是个什么东东。

  • 然后我才想起用真机跑一下呢,什么代码都不加,真机里面,切换到后台,没有任何一个layer调用removeAllAnimations。
  • 接着,我在界面上增加了一个UITextField,运行后点击UITextField让键盘弹出,然后切换到后台。
    控制台输出如下:
del07-10[1911:634198] Layer寻找:<CALayer: 0x17403b500>
del07-10[1911:634198] Layer寻找:<CALayer: 0x17402d340>
del07-10[1911:634198] Layer寻找:<CALayer: 0x17002f900>
del07-10[1911:634198] Layer寻找:<UIWindowLayer: 0x17002ec40>
del07-10[1911:634198] -[AppDelegate customMethod]开始
del07-10[1911:634198] self=<CALayer: 0x17403df20>
del07-10[1911:634198] -[AppDelegate customMethod]结束
del07-10[1911:634198] -[AppDelegate customMethod]开始
del07-10[1911:634198] self=<CALayer: 0x17403dfc0>
del07-10[1911:634198] -[AppDelegate customMethod]结束
del07-10[1911:634198] -[AppDelegate customMethod]开始
del07-10[1911:634198] self=<CALayer: 0x17403df40>
del07-10[1911:634198] -[AppDelegate customMethod]结束
...//若干次

也就是说我们试验到的调用removeAllAnimations的layer仅仅是和键盘相关的。
ps:在尝试的过程中,也怀疑过是否是present的转场动画会与我们写的冲突,所以系统才移除,尝试时不仅仅将animated设置为了NO,还使用了自定义的转场动画,发现都是不能执行的。

综述

经过这么一番折腾,我们可以知道的是当present一个viewController时,系统会移除该viewController的动画;当切换到后台是,系统也会移除当前动画。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,612评论 4 59
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,321评论 6 30
  • 昨天宿舍发现老鼠,我不禁想起了我家的猫。 想起我家的猫,心里有些失落。在我们家搬到县城后,每次回去,猫就很亲热的围...
    gardendong阅读 198评论 0 1
  • 有一种搞笑是我的乐观感染了你,有一种搞笑是我的丑陋给你嘲笑
    David_Yum阅读 95评论 0 0
  • 智障少女的自虐之旅 这是一个被称为“超燃脂室外跑步”的训练 4分钟慢跑,400米快跑,200米慢跑,400米快跑,...
    流星雨lxy阅读 1,646评论 1 0