Halting In Progress CAKeyframeAnimation - iphone

I'm animating a UIView's frame property using a CAKeyframeAnimation on the view's CALayer and animating the "position" property. I'd like to be able to halt the animation at it's current state when the user taps the screen, but I'm unable to do so.
I can halt the animation easily enough by calling
[view.layer removeAnimationForKey:kFrameAnimationKey];
The problem is that neither the view's frame not it's layer's position are updated directly by the animation. If I look at the position property at the time the animation starts and when it ends in
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
it has not changed.
It seems that you need to do that explicitly when the animation stops. But if the animation stops at some arbitrary point, you don't know how far it's gone. So the question is either how to make the animation update the layer's position property as it goes, or how to know how far the animation has gone when it's been stopped.

You can achieve this halting of the animation by grabbing the presentationLayer of your animating UIView's layer, then applying its frame to your UIView before removing the animation. For example:
movingView.frame = [[movingView.layer presentationLayer] frame];
[movingView.layer removeAnimationForKey:#"movementAnimation"];
This seems to provide the freezing of the UIView at the current animated position you're looking for.

Related

UIView 3D transform final frame location

I have a UIView which has an X origin that makes it off screen to the right. Then, I do a keyframe animation with CATransform3D:
[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-view.width, 0, 0)]
The problem is, after the animation completes, the view's frame is visually in the correct place, but it still thinks it's off screen, so I can't interact with it. Logging its frame property also shows that it's offscreen, but visually, it's not.
The fill mode for the animation is kCAFillModeForwards, so the final value of the animation sticks.
What is the solution to this problem, that is, interacting with this view after the animation and notifying the view that it is indeed visible?
You can use CAAnimation delegate method -animationDidStop:finished: to fix it when the animation done:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
...
}
The better answer is to use UIView animation methods like animateWithDuration:animations:completion: (and variants on that method.)
Those methods let you do animations on the actual view properties, and when the animation is complete, the view is at it's actual destination location.
The center property is the easiest way to move views around. The frame property isn't valid if you've applied a transform, but the center property IS always valid for both reading and writing.
Core Animation only creates the illusion that your views move. The animation takes place on a display-only copy of the layer tree. You can query the properties of an animating layer by reading those same properties from the layer's presentationLayer. However, the view objects will not respond to the user interaction at their apparent location. The views still think they are at their original locations.
As the other poster said, you could use the animation completion delegate method to move the view to it's final location once the animation is over, but that's a lot of fussy work to do something that UIView animation does for you much more cleanly and simply.
Can you interact with the view even before you animate it?
is the view.userInteractionEnabled is set to YES??

Determining which subview was touched

I'm trying out the animation stuff with the iPhone and I have a small app that has circles falling from the top of the screen to the bottom. I added the UIViewAnimationOptionAllowUserInteraction option to the animation and set userInteractionEnabled = YES on both my sub views and my main view. I am generating the subview programmatically everytime my NSTimer fires, which makes the circles.
The problem I am having is that even if I implement - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event on my main view and my subviews, the event only ever fires on the main view regardless of how hard or how many times I smash the falling circles.
How do I get that event to fire on my circles instead? Why doesn't the touch fire on the view on top?
The problem is that when you perform a "tween" animation with Core Animation (which occurs implicitly when you use the UIView animation methods), the new value is immediately applied to what's known as the model layer tree.
The animation is committed to Core Animation, and the next time the main run loop occurs, Core Animation does the necessary rendering to make it look as if an animation is occurring. In reality, though, the model values of your view (e.g., its frame or transform properties) are changed immediately.
Try tapping at the bottom of the screen where the buttons land (assuming they finish on screen). If you do this, it should respond to the interaction event as you expect.
You could manually change the frame of your circles by constantly firing a timer and applying small changes at each step. This, however, is likely not ideal.
Another option is to make use of your view's layer's presentationLayer method. This layer represents the state of the layer at any point in time, as we see it on the screen. Off the top of my head, I imagine that your main view's touch handling method can iterate through each subview and check to see if the touch point intersects the frame of the subview's layer's presentation layer. If it does, then that means someone "touched" your subview.
for (UIView *subview in self.subviews)
{
CALayer *presentationLayer = (CALayer *)[subview.layer presentationLayer];
if (CGRectContainsPoint(presentationLayer.frame, touchPoint)
{
// Found the subview the user touched
}
}
I should note that presentationLayer returns id, because the presentation layer may be CALayer or any of its subclasses. So, for example, if you're using CAShapeLayer, then -presentationLayer will return a CAShapeLayer. Still, it's fine to just cast it to a CALayer since all you care about for the purposes of this task is to check its frame property.

I want to animate, in fact fade, "within" drawRect

Is there a way to make drawRect animate FROM THE PREVIOUS SCENE to the next one?
(Amazingly) you can animate inside drawRect - try it. You can fade, translate or animate any other property.
However, it starts "from fresh", from blank. So for example if you put a fade-in animation block in drawRect, the previous scene with disappear and the new scene will fade up from white.
I want the screen to fade from the previous image (drawn in the previous cycle of drawRect) to the new image I have just drawn ... err, am drawing.
Is there a way to do that, perhaps trickily by manipulating what's going on with drawRect?
This would seem to be a very common use case - blending from one scene to the next.
Does anyone know the secret?
Of course, obviously this can be done in the core animation milieu or in many other ways, but having drawRect fade from one drawRect to the next is an obvious idea. Cheers.
Astounding update thanks to the genius of WrightCS.....
Thanks only to WrightCS, we now know that drawRect handles animations perfectly. Simply paste this code at the end of any drawRect and try it:
self.alpha = 0.0;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2];
self.alpha = 1.0;
[UIView commitAnimations];
it treats the entire drawRect, no matter how complex, as one enormous block and it wraps it in that animation. Yes, it even includes painted in offscreen areas, bitmap rendering or anything else. Everything gets animated. Who knew?
The problem at hand - how to make it start the animation from the previous scene rather than start from blank?
Drawrect Is invisible. It happens in the 'backbuffer' which the iOS displays on screen only when you're ready with drawRect. So you can definitely not animate while in drawrect. However, you can commit animation instrucions from just about anywhere, including drawrect. But the animation will be performed afterwards.
Animation requires timing and showing different frames to the user.
You CAN do that all by yourself (constantly forcing a redraw and doing something slightly different in drawrect each the time) but that's a lot of work, especialy if you want it done right.
Luckily iOS has many animation effects programmed for you. Either using Core Animation, or the (more simple and basic) animation in UIKit. But since it works by animating certain properties of views (eg the alpha of a whole view, or the rotation of a whole view, ...) you might need to rearrange your views and subviews to make good use of it.
E.g. Each horse limb is separate subview and you animate their transformations (no redraw needed, iOS will do the rest)
E.g. The old and new frame are two separate views and you animate the new frame (which is on top) from alpha 0 to alpha 1.
You can animate the alpha:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2.0];
myObject.alpha = 0.0;
[UIView commitAnimations];
if you are under iOS 5.0+, you can use the following:
...
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
//do your stuff here
}completion:^(BOOL finished) {
//completition stuff go here
}];
...
The best thing you can do is to use two UIView instances and animate between them.
When you are starting the animation you have to know which is the original and final state of the animation. That's difficult if it's only one view.

How can I callback as a CABasicAnimation is animating?

I have an animation I'm using to snap a rotatable UIView to a circular grid. As the view animates, I need to update another view based on the rotating views position.
How can I go about getting the position of the rotating view as it animates into position?
When animation starts you can start a function that will be polling current view position using a timer (I suppose you will need to get presentationLayer from view's layer and get position values from it).
There's no callbacks for CAAnimation other than animationDidStart and animationDidStop so it seems that's the solution (if I'm not mistaken it was also mentioned so in one of WWDC videos).

iPhone animation

I trying to do an animation on the iPhone and I hope someone can help me out here as I'm stumped.
I am using CAKeyframeAnimation with CAPath to animate views around.
I have these settings set:
animation.removedOnCompletion = YES;
animation.fillMode = kCAFillModeRemoved;
So that the animation is removed from the layer when the animation finishes I want the view to respond to touches. As I have found that if I set:
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
then the view no longer responds to touches after the animation.
This has also had the negative side effect of having to manually position the view to its finished location when the animation finishes.
The theory of what I'm doing works but in practice there is a jump of the view back to its original position before I can manually set its position in the animationDidStop method.
So is it possible to have the final position of animation stay and the view respond to touches?
Setting the fill mode to kCAFileModeForwards causes the animation to appear as if it is at the last step in the animation, but it is only the appearance. You actually need to set whichever property you are animating. If it's the position, you need to call:
[layer setPosition:destinationPoint];
on the layer. Then you don't have to set the fill mode or tell it not to remove the animation on completion. You will need to make sure that you set the animation for key "position" in order to override the default animation with your own. Call -setPosition before you add the animation to the layer. Something like this:
[layer setPosition:destinationPoint];
[layer addAnimation:kfAnimation forKey:#"position"];