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.
Related
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.
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.
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;
}
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
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!!!