iOS,退出后台时重新启动动画
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6042472/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iOS, Restarting animation when coming out of the background
提问by Stultus
when my app comes out of the background the animation has stopped. which is normal. but i want to restart my animation from the current state. how do i do that without my snapping all over the place.
当我的应用程序退出后台时,动画已停止。这是正常的。但我想从当前状态重新开始我的动画。我该怎么做才不会到处乱拍。
[UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
} completion:nil];
i tried putting a set frame in front of the animation but that just makes it snap.
我尝试在动画前面放置一个固定的框架,但这只会让它变得简单。
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
any ideas?
有任何想法吗?
回答by Ramiro
You can add an observer in your class for UIApplicationWillEnterForegroundNotification:
你可以在你的类中为 UIApplicationWillEnterForegroundNotification 添加一个观察者:
- (void)addNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)applicationWillEnterForeground {
[self animate];
}
- (void)animate {
[bg setFrame:CGRectMake(0, 0, 0, 0)];
[UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
} completion:nil];
}
It is important to set the begin state of the animation (and don't forget to remove the notification observer)
设置动画的开始状态很重要(不要忘记移除通知观察者)
回答by Jonathan
well the answer of @dany_23 could work.
好吧@dany_23 的答案可以工作。
But I came across an other method that works just fine if you don't need to resume your animation but restart your animation, without the view or layer snapping when you reactivate the app.
但是我遇到了另一种方法,如果您不需要恢复动画而是重新启动动画,并且在重新激活应用程序时没有视图或图层捕捉,则该方法效果很好。
in the
在里面
- (void)applicationWillResignActive:(UIApplication *)application
you call a method in your viewcontroller which implements the following code.
您在视图控制器中调用一个方法来实现以下代码。
[view.layer removeAllAnimations];
// this following CGRect is the point where your view originally started
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
and in the
并在
- (void)applicationDidBecomeActive:(UIApplication *)application
you call a method in your viewcontroller that just starts the animation. something like
您在视图控制器中调用一个方法来启动动画。就像是
[UIView animateWithDuration:60 delay:0 options:(UIViewAnimationOptionCurveLinear |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{
[bg setFrame:CGRectMake(0, 0, 1378, 1005)];
} completion:nil];
Hope this helps, Thanks to all who replied.
希望这有帮助,感谢所有回答的人。
回答by Grzegorz Krukowski
There is a better soloution here than restarting whole animation each time you come from background.
这里有比每次从后台重新启动整个动画更好的解决方案。
For Swift 3 you can subclass this class:
对于 Swift 3,您可以子类化此类:
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
}
}
It will take care of pausing all your animations in current state and re-adding it when app comes from background - without resetting them.
它将负责暂停当前状态下的所有动画,并在应用程序来自后台时重新添加它 - 无需重置它们。
Gist: https://gist.github.com/grzegorzkrukowski/a5ed8b38bec548f9620bb95665c06128
要点:https: //gist.github.com/grzegorzkrukowski/a5ed8b38bec548f9620bb95665c06128
回答by Steven Vandeweghe
回答by danny_23
Well if I understand the question right way, you're trying to create an animation and stop it in the middle.
好吧,如果我以正确的方式理解这个问题,那么您正在尝试创建一个动画并在中间停止它。
In this case i would suggest you to use some kind of "state" variable to store the state of animation (the frame property for example). This variable you can use in further code to put the object that is being animated in correct position. This can be done in animationDidStop function (animationDidStop:finished:). In order to implement this function you will have to use the delegation. It means to set AppNameViewController (or some other unique object for entire application) as a delegate for an animation and write the implementation in its m-file. This will allow you to run "position setup" code at the end of the animation.
在这种情况下,我建议您使用某种“状态”变量来存储动画状态(例如 frame 属性)。您可以在进一步的代码中使用此变量将正在设置动画的对象放置在正确的位置。这可以在 animationDidStop 函数(animationDidStop:finished:)中完成。为了实现这个功能,你必须使用委托。这意味着将 AppNameViewController(或整个应用程序的某些其他独特对象)设置为动画的委托,并将实现写入其 m 文件。这将允许您在动画结束时运行“位置设置”代码。
Next task is to store the animation state. While running animation, Core Animation framework creates a bunch of intermediate layers (presentation layers). And these layers are displayed during the animation and then they're deleted. And when animation finishes execution the object is just gets placed into it's final state. Actually you have to set this when you create so called "explicit animation" (and if you don't, animation will play but the object will jump back in the end). Each of these presentation layers has a set or it's own copy of all the properties that are called "animatable properties". When one of the animatable properties is set as a key for the animation, Core Animation calls (BOOL)needsDisplayForKeythat returns YES/NO. It tells the Core Animation whether changes to the specified key required the layer to be redisplayed. To "redisplay" means to call drawInContextmethod for the presentation layer. Here you can grab the properties of the presentation layer that is currently displayed and put them into "state" variable.
下一个任务是存储动画状态。在运行动画时,Core Animation 框架创建了一堆中间层(表示层)。这些图层在动画期间显示,然后被删除。当动画完成执行时,对象就会进入它的最终状态。实际上,您必须在创建所谓的“显式动画”时进行设置(如果不这样做,动画将播放但对象最终会跳回)。这些表示层中的每一个都有一组或它自己的所有属性的副本,这些属性称为“可动画属性”。当可动画属性之一被设置为动画的键时,Core Animation 调用(BOOL)needsDisplayForKey返回是/否。它告诉核心动画对指定键的更改是否需要重新显示图层。“重新显示”意味着为表示层调用drawInContext方法。在这里,您可以获取当前显示的表示层的属性并将它们放入“状态”变量中。
The animation is played in thread and in order to set the "state" variable I used delegation. It required to subclass CALayer (lets' say AnimLayer) and define a protocol in it with just one method that stores the "state". This method has one parameter which is the state. Then I implemented the protocol method that stores the state in AnimLayer class and set the unique object as a delegate. So those presentation layers (AnimLayer copies) do not store the state themselves. Instead the "state" value passed as a parameter to the delegate function is stored by unique object in main thread.
动画在线程中播放,为了设置“状态”变量,我使用了委托。它需要继承 CALayer(比方说 AnimLayer)并在其中定义一个协议,仅使用一种存储“状态”的方法。这种方法具有一个参数,即状态。然后我实现了在 AnimLayer 类中存储状态的协议方法,并将唯一对象设置为委托。所以那些表现层(AnimLayer 副本)本身并不存储状态。相反,作为参数传递给委托函数的“状态”值由主线程中的唯一对象存储。
I did something like that but my task was a bit different. I don't know the easier way, sorry. Hope this helps.
我做了类似的事情,但我的任务有点不同。我不知道更简单的方法,抱歉。希望这可以帮助。
回答by Mario Muniz
@Grzegorz Krukowski's answerworked like a charm for me. I just wanted to add that if you don't need to resume the animation from where it left off, but simulate it kept running on the background, you can simply omit these lines:
@Grzegorz Krukowski 的回答对我来说就像一种魅力。我只想补充一点,如果您不需要从停止的地方恢复动画,而是模拟它在后台继续运行,您可以简单地省略以下几行:
let timeSincePause: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
self.beginTime = timeSincePause
from the resume()
function in CALayer
extension, and leave beginTime
set to 0.0
.
从扩展中的resume()
功能CALayer
,并离开beginTime
设置为0.0
。
Also, you can manually call willResignActive
and didBecomeActive
methods for animations you want to resume when ViewController
disappears, even if the app didn't go to the background.
此外,还可以手动调用willResignActive
,并didBecomeActive
要继续为动画方法时ViewController
消失,即使应用程序没有去后台。
回答by rbbtsn0w
This way will be better, just register on ApplicationDelegate will become methods and observer the status.
这种方式会更好,只需在 ApplicationDelegate 上注册将成为方法并观察状态。
- (void)pauseAnimate{
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)stopAnimate{
CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = pausedTime;
}
回答by Andres Canella
As of the release of swift5.
随着swift5的发布。
It is also worth noting. That in some cases, you need to reset the animated property to get the new animation to stick. I can confirm this with an animated transformation.
这也值得注意。在某些情况下,您需要重置动画属性以使新动画保持不变。我可以通过动画转换来确认这一点。
Just calling...
就是打电话...
view.layer.removeAllAnimations()
did not suffice, and new animation would not run. I also had to run...
还不够,新动画将无法运行。我也不得不跑...
view.transform = .identity
I suspect you might be able to set it to any new value or save the previous state and set it.
我怀疑您可以将其设置为任何新值或保存以前的状态并进行设置。
回答by Joris Mans
I don't recall the exact details but I think you can get the current animated position of the frame by looking at the layer bounds and position property of your view. You could store those at app suspend and restore them when the app is in the foreground again.
我不记得确切的细节,但我认为您可以通过查看视图的图层边界和位置属性来获得帧的当前动画位置。您可以将它们存储在应用程序挂起时,并在应用程序再次处于前台时恢复它们。