I have 2 UIImageView's in the main view controller of my app. They are always animating via UIView animations. What I want to achieve is when the user clicks the home button, pause the UIView animations, and when the user comes back to the app, resume the animations.
So to pause I call a method from my app delegate to my ViewController to pause in the applicationWillResignActive call.
And to resume I call a method also from the app delegate to my ViewController to resume in the applicationDidBecomeActive method.
This is the code I use to either pause or resume my animations:
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval paused_time = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = paused_time;
}
-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval paused_time = [layer timeOffset];
layer.speed = 1.0f;
layer.timeOffset = 0.0f;
layer.beginTime = 0.0f;
CFTimeInterval time_since_pause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - paused_time;
layer.beginTime = time_since_pause;
}
- (void)pause:(BOOL)theBool {
if (theBool) {
[self pauseLayer:self.myImageView.layer];
[self pauseLayer:self.myImageViewleft.layer];
} else {
[self resumeLayer:self.myImageView.layer];
[self resumeLayer:self.myImageViewleft.layer];
}
}
So I NSLogged everything and the methods get called accordingly however when I go to resume the animations, the image views don't resume from where they left off and are most likely at the position I placed them in the XIB (off the screen).
So when I re-enter my app, does my XIB get reloaded or something? There is absolutely nothing that should be changing the position of those UIImageView's, thats why I am really confused why this is happening.
Edit: I have also tried creating my images programmatically, no luck same issue. The pausing and resuming works successfully if I do it via buttons in my app while its running, it seems the problem is with the application delegate methods or something.
If anyone sees what's wrong, please let me know!
I found this to be really complicated and tricky so for now I will just use Cocos2D instead and make use of CCDirector's pause feature.
Edit: Just tried CCDirector's pause feature and it works perfectly. Definitely go this route!!!
Related
I have a problem with disappearing objects after animation ends. It's all fine if animation gets to its end. But I have animation which is connected to UIPanGesturerecognizer. If user pans more than 33% of screen width, animation goes on. If not, animation gets speed = -1 and actually gets to its beginning.
The question is: how to force layer to redraw?
When I try to start new animation, object appears.
I tried call setNeedsDisplay: in - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
, but with no luck.
EDIT
Here is some code. I put just one animation for the sake of cleanness. Everything works.
So, first, here is my gesture handler.
When the gesture is in UIGestureRecognizerStateChanged state, we stop the animation by setting layer's speed and control execution of animation by setting layer's timeoffset.
When the gesture is in UIGestureRecognizerStateEnded state, we have to check whether translation of pan on x is more than 33% of screen width. If it is we call - (void)finishLayer:(CALayer*)layer animation:(CAKeyframeAnimation*)animation withForward:(BOOL)shouldGoForward
with YES which means animation should continue to next screen, otherwise, we pass NO which means animate backwards to inital state.
- (void)panGestureHandler:(UIPanGestureRecognizer*)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded)
{
//if our gesture was enough to let animation roll to its end
if (fabsf(translation.x) > CGRectGetWidth(self.view.frame) * 0.33)
{
[self finishLayer:categoryControllerMain.view.layer animation:menuOpenSwipeAnimateMoveLeft withForward:NO];
}
//finger movement was below .33% of screen width so animate to initial state
else
{
[self finishLayer:categoryControllerMain.view.layer animation:menuOpenSwipeAnimateMoveLeft withForward:NO];
}
}
else if (recognizer.state == UIGestureRecognizerStateChanged)
{
//We put layer speed to zero so we can control animation with timeoffset in relation to finger pan movement
categoryControllerMain.view.layer.speed = 0.0;
[categoryControllerMain.view.layer addAnimation:menuOpenSwipeAnimateMoveLeft forKey:#"menuOpenMoveLeft"];
categoryControllerMain.view.layer.timeOffset = fabs(translation.x / CGRectGetWidth(self.view.frame));
}
}
Here is the method which sends animation to its end by setting layer's speed back to value 1.0 or if we want to animate backwards, we set speed to -1.0. We also want to set beginTime so that after we end pan gesture, animation knows exact time from which to continue.
- (void)finishLayer:(CALayer*)layer animation:(CAKeyframeAnimation*)animation withForward:(BOOL)shouldGoForward
{
pan.enabled = NO;
if (shouldGoForward)
{
//animation continues
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
layer.speed = 1.0;
}
else
{
//we want to take it back
layer.timeOffset = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.beginTime = CACurrentMediaTime();
layer.speed = -1;
}
}
So, in case animation needs to run backwards, it really does, but when it's over, everything disappears. In case of forward animation, everything is in its place. After adding animation, I didn't change layer's position, and I don't use fillMode on animation, so after animation ends, everything should be as it was before animation started, everything happens on presentation layer of animation.
Here are some screenshots. There is pan gesture going on on first image. Since finger moves less than 33% of screen width, this where backwards animation should start. And it does. We animate UITableView which is empty now, white UILabel and grayish UIImageView in the background.
After pan gesture ends, animation starts rolling backwards and when it gets to its end, everything what animated just disappears. On the other hand, if we pan over 33% of screen width, animation gets forward and I can see my UITableView. Here is the photo of backwards animation end.
If I need simply to pause/resume rotation then I use the following code:
- (void)pauseAnimations
{
CFTimeInterval paused_time = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = paused_time;
}
- (void)resumeAnimations
{
CFTimeInterval paused_time = [self.layer timeOffset];
self.layer.speed = 1.0f;
self.layer.timeOffset = 0.0f;
self.layer.beginTime = 0.0f;
CFTimeInterval time_since_pause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - paused_time;
self.layer.beginTime = time_since_pause;
}
But I want to stop the current layer animation before rotation and resume after it. And this code stops the rotation process too.
How to solve this issue?
You can't pause a layer's animation's all but one by setting the layer's speed to 0.
You might be able to fetch the active animations from the layer and pause the individual animations using the technique you are using to pause animations on the whole layer (setting the speed to 0).
Note that you can't save a pointer to the animation objects that you create because the system actually adds a copy of the animation to the layer, not the original animation. You would probably need to fetch the animation using the CALayer animationForKey method.
Disclaimer: I have never tried this. My answer is based on general knowledge of how Core Animation works.
What I want to do is call a method after a delay for a game which will reload a weapon, but if I do that and the user pauses the game that selector isn't getting paused so the user can cheat by pausing.
So Im wondering if there is a way to pause it and then continue.
I know how to cancel the selector with this: cancelPreviousPerformRequestsWithTarget: but I want it to be able to continue after the user resumes the game.
Is this the right approach or should I consider another strategy?
This is absolutely not a good way to solve this problem, but it did the trick for me.
I've got a scaling animation, where a view grows to scale 6.0, waits for 2 seconds and then scales back to 1.0.
The wait for 2 seconds is my part where I used the performSelector:withObject:afterDelay: which I couldn't pause.
This is the pause part:
[UIView animateWithDuration:0 delay:2.0 options:0 animations:^{
self.transform = CGAffineTransformMakeScale(6.0 + 0.001, 6.0 + 0.001);
} completion:^(BOOL finished) {
[self animateOutWithCompletion:completion];
}];
Make sure you are animation a change, thats why I add 0.001 (which you wont see, as it's a that little change).
UIView animations can be paused using this category:
#interface UIView (AnimationsHandler)
- (void)pauseAnimations;
- (void)resumeAnimations;
#end
#implementation UIView (AnimationsHandler)
- (void)pauseAnimations
{
CFTimeInterval paused_time = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = paused_time;
}
- (void)resumeAnimations
{
CFTimeInterval paused_time = [self.layer timeOffset];
self.layer.speed = 1.0f;
self.layer.timeOffset = 0.0f;
self.layer.beginTime = 0.0f;
CFTimeInterval time_since_pause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - paused_time;
self.layer.beginTime = time_since_pause;
}
Feel free to use this code, but as I said before, this is not a good way to solve this problem. It's just making use of the way UIView animations work and the possibility to pause these animations.
I have an animation on a CALayer which repeats indefinitely however I want to change it such that the animation is triggered by a certain timing event.
I can get it to work by removing and re-adding the animation event when the trigger occurs but this seems a bit kludgy, is there an alternative way without having to constantly remove and add the animation all the time?
Here's the code in sketch form:
- (void) init
{
….
CALayer *sublayer = …
[self.layer addSublayer:sublayer];
[self createAnimationGroup];
[sublayer addAnimation: self.animationGroup forKey:#"MyKey"];
}
- (void) createAnimationGroup
{
CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
self.animationGroup = [CAAnimationGroup animation];
self.animationGroup.repeatCount = 0.0f; // was previously INFINITY;
<snip>
NSArray *animations = #[scaleAnimation, opacityAnimation];
self.animationGroup.animations = animations;
}
- (void) onTrigger
{
[self.layer.sublayers[0] removeAnimationForKey:#"MyKey"];
[self.layer.sublayers[0] addAnimation:self.animationGroup forKey:#"MyKey"];
}
My question is is the way I have implemented onTrigger ok, it works, but is there a way of triggering the animation directly rather than indirectly via removing and adding it?
Take a look at this suggestion from Apple. Specifically pausing and resuming an animation.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html#//apple_ref/doc/uid/TP40004514-CH8-SW15
I am creating a game in which i have to move coveyner belt continuously from top to bottom.The problem arises when my app goes into background and is resumed later.
The object is removed from current position, and the animation stops.
As the comments are describing you basically can't do it, at least not in an strait-forward way.
Let me show you how I would approach the following problem:
The main problem that you have is that when an object is being animated the actual position of the object doesn't really change. What changes is the presentation layer instead!
So, how can you know where the objects are exactly? Well, theoretically you can do so the by asking the layers presentation layer for the actual frame, anchorpoint, or whatever property it is that you are animating:
[[myView.layer presentationLayer] frame];
So, what I would do is when the application goes to background I would set update all the views to reflect the presentation layer. When you come back, I would resume the animations from there. The objects should be in the right place.
NSAarray *viewsBeingAnimated;
- (void)applicationWillEnterForeground:(UIApplication *)application{
for (UIView *oneView in viewBeingAnimated){
// Update whatever other properties that you were animating
[oneView setFrame:[[myView.layer presentationLayer] frame]];
[oneView layer] removeAllAnimations];
}
}
Now, once the app has resumed, you should be able to continue from there.
Hope that helps.
-(void)pauseLayer:(CALayer*)layer{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;}
#pragma mark -resume game
-(void)resumeLayer:(CALayer*)layer{
NSLog(#"paused:%f",[layer timeOffset]);
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;}
I am using this code and i'm using block based animation to animate thousands of image-one at time falling from top to bottom of iPhone screen