当应用程序从后台恢复时,在animation停止的位置恢复

在我看来,我有一个无止境的循环的CABasicAnimation重复的图像瓷砖:

 a = [CABasicAnimation animationWithKeyPath:@"position"]; a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; a.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; a.toValue = [NSValue valueWithCGPoint:CGPointMake(image.size.width, 0)]; a.repeatCount = HUGE_VALF; a.duration = 15.0; [a retain]; 

我试图按照技术问答QA1673中的描述“暂停和恢复”图层animation。

当应用程序进入后台时,animation将从图层中移除。 为了弥补,我听取了UIApplicationDidEnterBackgroundNotification并调用stopAnimation并响应UIApplicationWillEnterForegroundNotification调用startAnimation

 - (void)startAnimation { if ([[self.layer animationKeys] count] == 0) [self.layer addAnimation:a forKey:@"position"]; CFTimeInterval pausedTime = [self.layer timeOffset]; self.layer.speed = 1.0; self.layer.timeOffset = 0.0; self.layer.beginTime = 0.0; CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; self.layer.beginTime = timeSincePause; } - (void)stopAnimation { CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil]; self.layer.speed = 0.0; self.layer.timeOffset = pausedTime; } 

问题是,它从头开始,从最后一个位置开始有一个丑陋的跳转,就像在应用程序进入后台时应用程序快照中看到的那样,回到animation循环的开始处。

当我重新添加animation时,我无法弄清楚如何使它从最后位置开始。 坦率地说,我只是不明白QA1673的代码是如何工作的:在resumeLayer它将resumeLayer设置为两次,这似乎是多余的。 但是,当我删除了第一个设置为零时,它不会恢复暂停的animation。 这是用简单的水龙头手势识别器testing的,它确实切换了animation – 这与我从背景中恢复的问题没有严格的关系。

在animation被删除之前我应该​​记住什么状态,以后如何重新添加animation,如何从该状态恢复animation?

经过与iOS开发专家的大量search和交stream之后,看起来QA1673在暂停,背景,然后移动到前景方面没有任何帮助。 我的实验甚至表明,animation引发的委托方法(如animationDidStop变得不可靠。

有时他们会开火,有时他们不会。

这就产生了很多问题,因为这意味着你不仅在看着你停下来的不同的屏幕,而且当前运动的事件顺序也会中断。

我到目前为止的解决scheme如下:

当animation开始时,我得到开始时间:

mStartTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];

当用户点击暂停button时,我从CALayer删除animation:

[layer removeAnimationForKey:key];

我使用CACurrentMediaTime()获取绝对时间:

CFTimeInterval stopTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];

使用mStartTimestopTime计算偏移时间:

 mTimeOffset = stopTime - mStartTime; 

我也将对象的模型值设置为presentationLayer的模型值。 所以,我的stop方法如下所示:

 //-------------------------------------------------------------------------------------------------- - (void)stop { const CALayer *presentationLayer = layer.presentationLayer; layer.bounds = presentationLayer.bounds; layer.opacity = presentationLayer.opacity; layer.contentsRect = presentationLayer.contentsRect; layer.position = presentationLayer.position; [layer removeAnimationForKey:key]; CFTimeInterval stopTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; mTimeOffset = stopTime - mStartTime; } 

在简历上,我根据mTimeOffset重新计算暂停animation的mTimeOffset 。 这有点乱,因为我正在使用CAKeyframeAnimation 。 我根据mTimeOffset找出哪些关键帧是优秀的。 另外,我考虑到暂停可能发生在中间帧,例如在f1f2之间的中途。 那个时间是从那个关键帧的时间中扣除的。

然后我将这个animation重新添加到图层:

[layer addAnimation:animationGroup forKey:key];

另外要记住的是,你将需要检查animationDidStop的标志,并且如果标志是YES ,则只使用removeFromSuperlayer从父级删除animation层。 这意味着图层在暂停期间仍然可见。

这个方法似乎很费力。 它虽然工作! 我希望能够使用QA1673简单地做到这一点 。 但是,目前的背景,这是行不通的,这似乎是唯一的解决办法。

嘿,我在我的游戏中偶然发现了同样的事情,最终find了一个与你不同的解决scheme,你可能会喜欢:)我想我应该分享我find的解决方法…

我的情况是使用UIView / UIImageViewanimation,但它的核心基本上仍然是CAAnimations …我的方法的要点是,我复制/存储当前的animation视图,然后让苹果暂停/继续工作,但之前恢复我重新添加我的animation。 所以让我来举个简单的例子:

比方说,我有一个名为movingView的UIView。 UIView的中心通过标准的[ UIView animateWithDuration … ]来调用。 使用提到的QA1673代码,它可以很好地暂停/恢复(当不退出应用程序时)…但是无论如何,我很快意识到,在退出时,无论是否暂停,animation都被完全删除了……在这里,我是在你的位置。

所以在这个例子中,我做了以下操作:

  • 在头文件中有一个名为类似于* CAAnimation **的animationViewPosition的variables。
  • 当应用程序退出到后台,我这样做:

     animationViewPosition = [[movingView.layer animationForKey:@"position"] copy]; // I know position is the key in this case... [self pauseLayer:movingView.layer]; // this is the Apple method from QA1673 
    • 注意:那些2 ^调用是在UIApplicationDidEnterBackgroundNotification (类似于你)的处理程序的方法中,
    • 注2:如果你不知道关键是什么(你的animation),你可以遍历视图的图层的“ animation键 ”属性,并logging下来(大概中间animation)。
  • 现在在我的UIApplicationWillEnterForegroundNotification处理程序中:

     if (animationViewPosition != nil) { [movingView.layer addAnimation:animationViewPosition forKey:@"position"]; // re-add the core animation to the view [animationViewPosition release]; // since we 'copied' earlier animationViewPosition = nil; } [self resumeLayer:movingView.layer]; // Apple's method, which will resume the animation at the position it was at when the app exited 

而这就是它! 它为我工作到目前为止:)

只需对每个animation重复这些步骤,就可以轻松地将其扩展为更多的animation或视图。 它甚至适用于暂停/恢复UIImageViewanimation,即标准的[ imageView startAnimating ]。 那个层的animation键(顺便说一下)是“内容”。

清单1暂停和恢复animation。

 -(void)pauseLayer:(CALayer*)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime; } -(void)resumeLayer:(CALayer*)layer { CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause; } 

令人惊讶的是,这不是更直接。 我根据cclogg的方法创build了一个类别,这个类别应该是单行的。

CALayer的+ MBAnimationPersistence

设置好所需的animation后,只需在图层上调用MB_setCurrentAnimationsPersistent

 [movingView.layer MB_setCurrentAnimationsPersistent]; 

或者指定应该明确保持的animation。

 movingView.layer.MB_persistentAnimationKeys = @[@"position"]; 

我不能发表评论,所以我会将其添加为答案。

我使用cclogg的解决scheme,但是当animation的视图从他的超级视图中删除时,我的应用程序崩溃,再次添加,然后去背景。

将animation.repeatCount设置为Float.infinity ,将animation.repeatCount设为无限。
我的解决scheme是将animation.removedOnCompletion设置为false

这是非常奇怪的,因为animation永远不会完成。 如果有人有解释,我喜欢听。

另一个提示:如果您从超级视图中删除视图。 不要忘记通过调用NSNotificationCenter.defaultCenter().removeObserver(...)来移除观察者。

我能够通过保存当前animation的副本并将其添加到简历中来恢复animation(但不是animation位置)。 我在加载时调用startAnimation,在进入前景时暂停,在进入后台时暂停。

 - (void) startAnimation { // On first call, setup our ivar if (!self.myAnimation) { self.myAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; /* Finish setting up myAnimation */ } // Add the animation to the layer if it hasn't been or got removed if (![self.layer animationForKey:@"myAnimation"]) { [self.layer addAnimation:self.spinAnimation forKey:@"myAnimation"]; } } - (void) pauseAnimation { // Save the current state of the animation // when we call startAnimation again, this saved animation will be added/restored self.myAnimation = [[self.layer animationForKey:@"myAnimation"] copy]; } 

以防万一任何人需要Swift 3解决scheme来解决这个问题:

你所要做的就是从这个类中inheritance你的animation视图。 它始终坚持并恢复它的图层上的所有animation。

 class ViewWithPersistentAnimations : UIView { private var persistentAnimations: [String: CAAnimation] = [:] private var persistentSpeed: Float = 0.0 override init(frame: CGRect) { super.init(frame: frame) self.commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.commonInit() } func commonInit() { NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) } deinit { NotificationCenter.default.removeObserver(self) } func didBecomeActive() { self.restoreAnimations(withKeys: Array(self.persistentAnimations.keys)) self.persistentAnimations.removeAll() if self.persistentSpeed == 1.0 { //if layer was plaiyng before backgorund, resume it self.layer.resume() } } func willResignActive() { self.persistentSpeed = self.layer.speed self.layer.speed = 1.0 //in case layer was paused from outside, set speed to 1.0 to get all animations self.persistAnimations(withKeys: self.layer.animationKeys()) self.layer.speed = self.persistentSpeed //restore original speed self.layer.pause() } func persistAnimations(withKeys: [String]?) { withKeys?.forEach({ (key) in if let animation = self.layer.animation(forKey: key) { self.persistentAnimations[key] = animation } }) } func restoreAnimations(withKeys: [String]?) { withKeys?.forEach { key in if let persistentAnimation = self.persistentAnimations[key] { self.layer.add(persistentAnimation, forKey: key) } } } } extension CALayer { func pause() { if self.isPaused() == false { let pausedTime: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) self.speed = 0.0 self.timeOffset = pausedTime } } func isPaused() -> Bool { return self.speed == 0.0 } func resume() { let pausedTime: CFTimeInterval = self.timeOffset self.speed = 1.0 self.timeOffset = 0.0 self.beginTime = 0.0 let timeSincePause: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) - pausedTime self.beginTime = timeSincePause } } 

在要点: https : //gist.github.com/grzegorzkrukowski/a5ed8b38bec548f9620bb95665c06128

我使用cclogg的解决scheme,效果很好。 我也想分享一些其他的信息,可能会帮助别人,因为它让我感到沮丧了一段时间。

在我的应用程序,我有一些animation,一些永远循环,一些只运行一次,随机产生。 cclogg的解决scheme为我工作,但是当我添加了一些代码

 - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag 

为了在只有一次animation完成时做某些事情,只要这些特定的一次性animation在暂停时运行,那么当我恢复我的应用(使用cclogg的解决scheme)时,这个代码就会触发。 所以我添加了一个标志(我的自定义UIImageView类的成员variables),并将其设置为YES,以恢复所有图层animation( resumeLayer中的resumeLayer,类似于Apple解决schemeQA1673),以防止发生这种情况。 我为每个正在恢复的UIImageView执行此操作。 然后,在animationDidStop方法中,只有当该标志为NO时才运行一次性animation处理代码。 如果是,则忽略处理代码。 将标志切换回NO。 这样,当animation真正完成时,您的处理代码将运行。 所以像这样:

 - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag if (!resumeFlag) { // do something now that the animation is finished for reals } resumeFlag = NO; } 

希望能帮助别人。