Pause and resume animations except of rotation in iOS? - ios

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.

Related

Core Animation short hand causes odd behaviour [duplicate]

Here's some relevant code inside a UIView subclass:
- (void) doMyCoolAnimation {
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"position.x"];
anim.duration = 4;
[self.layer setValue:#200 forKeyPath:anim.keyPath];
[self.layer addAnimation:anim forKey:nil];
}
- (CGFloat) currentX {
CALayer* presLayer = self.layer.presentationLayer;
return presLayer.position.x;
}
When I use [self currentX] while the animation is running, I get 200 (the end value) rather than a value between 0 (the start value) and 200. And yes, the animation is visible to the user, so I'm really confused here.
Here's the code where I call doMyCoolAnimation:, as well as currentX after 1 second.
[self doMyCoolAnimation];
CGFloat delay = 1; // 1 second delay
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(#"%f", [self currentX]);
});
Any ideas?
I don't know where the idea for using KVC setters in animation code came from, but that's what the animation itself is for. You're basically telling the layer tree to immediately update to the new position with this line:
[self.layer setValue:#200 forKeyPath:anim.keyPath];
Then wondering why the layer tree won't animate to that position with an animation that has no starting or ending values. There's nothing to animate! Set the animation's toValue and fromValue as appropriate and ditch the setter. Or, if you wish to use an implicit animation, keep the setter, but ditch the animation and set its duration by altering the layer's speed.
My UIView's layer's presentationLayer was not giving me the current values. It was instead giving me the end values of my animation.
To fix this, all I had to do was add...
anim.fromValue = [self.layer valueForKeyPath:#"position.x"];
...to my doMyCoolAnimation method BEFORE I set the end value with:
[self.layer setValue:#200 forKeyPath:#"position.x"];
So in the end, doMyCoolAnimation looks like this:
- (void) doMyCoolAnimation {
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"position.x"];
anim.duration = 4;
anim.fromValue = [self.layer valueForKeyPath:anim.keyPath];
[self.layer setValue:#200 forKeyPath:anim.keyPath];
[self.layer addAnimation:anim forKey:nil];
}
The way you are creating your animation is wrong, as CodaFi says.
Either use an explicit animation, using a CABasicAnimation, or use implicit animation by changing the layer's properties directly and NOT using a CAAnimation object. Don't mix the two.
When you create a CABasicAnimation object, you use setFromValue and/or setToValue on the animation. Then the animation object takes care of animating the property in the presentation layer.

Pause and resume a performSelector afterDelay?

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.

animated by percentage before iOS 7

UIPercentDrivenInteractiveTransition is a transformation available in iOS 7. Is it possible to implement UIPercentDrivenInteractiveTransition prior iOS 7?
For Example, can I do the same updating percentage of animation when using CAAnimation and UIPanGestureRecognizer in iOS 6?
UPDATE:
I can follow panGesture to updating percentage of animation, and finish the whole animation after my finger up:
CFTimeInterval pausedTime = [self.transitionView.layer timeOffset];
self.transitionView.layer.speed = 1.0;
self.transitionView.layer.timeOffset = 0.0;
self.transitionView.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [self.transitionView.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
self.transitionView.layer.beginTime = timeSincePause;
But if I wanna to cancel the animation, how should I do?
My idea is setting autoreverses to yes, and calculating timeOffset to what I expect. For example, animation duration is 1s, and total time is 2s, if I want to revers animation when timeOffset is 0.5, the same status at reverse progress is 1.5. This is my code and it did not work:
self.animation.autoreverses = YES;
self.transitionView.layer.timeOffset = 2 - percent; // duration is 1s, percent is current time offset
CFTimeInterval pausedTime = [self.transitionView.layer timeOffset];
self.transitionView.layer.speed = 1.0;
self.transitionView.layer.timeOffset = 0.0;
self.transitionView.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [self.transitionView.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
self.transitionView.layer.beginTime = timeSincePause;
Check out what I wrote about animation timing and scroll down to "Controlling animation timing". There is an accompanying example on GitHub that uses scroll events to manually drive an animation from the scroll events.
You can use the same technique (setting the timeOffset on the layer) with a gesture recognizer to drive the animation.
There is also a much simpler example in my answer here that uses a slider and its action to control the animation.

Stopping core animation on layer when some condition is met

I'm trying to make 2 core animations that start at the same time and use the same curve but one of them need to be stopped if the condition is met. E.g. I have the following view on the left and want to transition to the right one.
The red views can be treated as overlays on top of yellow views even though in the beginning they are not intersecting. Imagine clicking 'item 3'. What I need to do is to animate movement of all rows to the top with distance -item3.frame.origin.y but once title3 reaches top of the screen it should stay there so the remaining item1 item2 will look like they are sliding behind title3
I tried to animate all elements using core animation with option: UIViewAnimationsOptionLayoutSubviews and then in layoutSubview stop the animation with the following code:
-(void)layoutSubviews {
CALayer* presentationLayer = (CALayer*)[self.title3.layer presentationLayer];
if (presentationLayer.frame.origin.y <= 0) {
[self.title3.layer removeAllAnimations];
}
}
but without any success. Is there any way to make it work using core animation and without mocking with CADisplayLink. I can't use two seperate animations because the duration for title3 animations is hard to calculate which creates a gap between title3 and title2 if the animation curve is not linear.
You can implement pause/start layer animation feature according Apple's technical note QA1673 by following code:
-(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;
}

How to create a continuous animation in iOS which will also run when app in background?

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

Resources