Core Animation retargeting (in iOS?) - ios

I'm reasonably new to iOS/Obj-C/Cocoa development (day 5!), so I'm sure I'm probably missing something basic here, but anyway…
Say I have an animation (a CABasicAnimation) to change the position.y property of my CALayer. I set the duration to 10 seconds, and start the animation. 5 seconds later, due to user interaction, I want to change the destination position.y of the animation.
I have tried two approaches:
Change the toValue of the in-flight animation
This simply resulted in no change to the animation (though obviously it "jumped" to the actual position when it was over)
Cancel the running animation (removeAnimationForKey:), and start a new one. The issue here is I have no way to get the current position.y from the running animation for the new animation.
I see (brief/vague) mention of "animation retargeting" in the Core Animation documentation for OS X, but no such mention in iOS.
Is this possible?

try to start the next animation with fromValue = [[layer presentationLayer] position].y;
(as answered in the comments above by #phix23)

Related

Observe progress of UIView.animateWithDuration/CABasicAnimation?

Is there a way to observe the "time progress" of UIView.animateWithDuration...family of methods from UIView /alternatively CA animations?
I am animating a view's frame and I need to be informed how it is progressing.
My line of thinking was I can either
1) tap into CAAnimation related stuff or
2) observe the animated properties (like frame) and do my own calculations each screen frame.
Approach 1) turns out to be a dead end, inspecting the internal of how CAAnimations work told me absolutely nothing...and 2) is flawed as the "model layer tree is updated immediately and tapping into the presentation tree is difficult as the presentation layer is nil when you start.
I am pretty desperate, I was thinking that hooking into CADisplayLink will give me a tick and then I simply check something that is affected by the animation but there is nothing to tap to.
Do you think going the NSTimer way that is launched in the same scope as the animation method is ok? If I know animation duration then I can generate the progress myself.
If all you want is the time value, then you can do math on the CACurrentMediaTime() minus the animation start time. I have a sample project on Github called KeyframeViewAnimations that does exactly that.
That project supports pausing and resuming and scrubbing both UIView and CAAnimation based animations. In both cases it digs into the underlying CAAnimations.
I have another project that uses the values of the animated layer's presentationLayer in order to do hit testing so you can tap on an in-flight view and start/pause the animation. That one can be found here:
iOS-CAAnimation-group-demo
My code uses an NSTimer to update the progress of the animation. It would be better to use a CADisplayLink timer, as you mentioned.
I am also looking at the new UIViewPropertyAnimator class that was added to iOS 10. That makes pausing, reversing, and scrubbing UIView animations easy without having to dig into the underlying CAAnimations. See this thread I just posted:
Is there a way to observe changes to fractionComplete in UIViewPropertyAnimator

How to implement a D-Pad Xcode 5 on an iPad

I have tried many times to implement a D-Pad for my new maze game, but am having trouble doing so. I am doing it in the style of 4 UIButtons and if you press one, it moves another image up, down, left or right. I have tried using the quartzcore and CAAnimation, but only know limited code.
I have the methods and the buttons declared, but cannot code a working one.
I was doing:
-(IBAction)Up:(id)sender{
CGPoint origin1 = self.Player.center;
CGPoint target1 = CGPointMake(self.Player.center.x, self.Player.center.y-124);
CABasicAnimation *bounce1 = [CABasicAnimation animationWithKeyPath:#"position.y"];
bounce1.duration = 0.1;
bounce1.fromValue = [NSNumber numberWithInt:origin1.y];
bounce1.toValue = [NSNumber numberWithInt:target1.y];
[self.Player.layer addAnimation:bounce1 forKey:#"position"];
}
The ghost moves down, but bops back up immediately. I've been stumped for hours and it may be glaring me in the face, but please understand my noobishness.
In Core Animation, there are two layer hierarchies: one of model layers and one of presentation layers. Presentation layers are what you see; model layers are what you often interact with in code. This separation is valuable because it lets you create implicit animation -- e.g. set the position of a layer, and it'll animate to the new spot. (But if you assign a value to position, you want that to be the value you read back immediately thereafter, even if the animation is still in progress.)
When you use addAnimation:forKey:, you're affecting the presentation, not the model. So, during the animation, you see the Player layer moving, but that layer is "really" still where you left it. As soon as the animation ends, it's removed from the layer, so the presentation matches the model again -- you see it at its original position.
If you want the model position to change as well, you need to change it separately:
self.player.layer.position = CGPointMake(self.player.layer.position.x, target1.y);
You can either update the model immediately after adding the animation (you'll see the change after the animation completes), or use a completion block to schedule it at the end of the animation. There are subtleties to consider for either approach.
Often, though, if you just want to animate a specific change like that, especially for the main layer of a UIView, it's simpler to use the implicit animation API on UIView:
[UIView animateWithDuration:0.1 animations: ^{
self.player.center = CGPointMake(self.player.center.x, target1.y);
}];
This will both update the model value and create and run an animation for moving from the current position to the target position.
BTW: It helps to follow Cocoa code style conventions: use initial caps only for class names or other type names, and lowercase for variable/property/method names.

Setting alpha to 0 to remove animation? -objective c

I'm developing an iOS application and I have a simple question.
If you start an animation for an image (like rotating it) for an unlimited time, and then after a few seconds set the alpha to zero to hide it [[self image]setAlpha:0];. Is the animation still working in the background or not? Do you always have to set [layer removeAllAnimations];? I'm asking this because I don't wan't to slow down the cpu.
Thanks!
You really want to use removeAllAnimations, not just setting the alpha to zero. If you use a timer or display link to monitor the presentationLayer (which indicates the current properties of the view) of the animated object, you'll see that the animation is still in progress, even if the alpha is zero.

How to make UI object responsive after CABasicAnimation

I'm having trouble trying to find a way to make my UI object (UISegmentedControl) touch responsive after a CABasicAnimation slide in and bounce. How can I pull this off?
I know the UI object is in the presentation tree after the move. But I really like the setTimingFunction feature CABasicAnimation provides and I just won't be able to get such a smooth bounce using UIView animation.
Example GIF of animation (Looped):
Code I'm using inside viewDidLoad
CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
[startAnimation setFromValue:[NSNumber numberWithFloat:0]];
[startAnimation setToValue:[NSNumber numberWithFloat:slidingUpValue]];
[startAnimation setDuration:1.0];
startAnimation.fillMode = kCAFillModeForwards;
startAnimation.removedOnCompletion = NO;
[startAnimation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:0.0 :0.0 :0.3 :1.8]];
[[gameTypeControl layer] addAnimation:startAnimation forKey:nil];
What went wrong
The problem is these two lines of code and not understanding the side effects of using them:
startAnimation.fillMode = kCAFillModeForwards;
startAnimation.removedOnCompletion = NO;
The first line configures the animation to keep showing the end value after the animation has completed (you can see a visualization of fillMode and the other timing related properties in my little cheat sheet).
The second line configures the animation to stay attached to the layer after it finishes.
This sounds just fine at first, but is missing an essential part of Core Animation: animations added to a layer never affect the model, only the presentation. The Core Animation Programming Guide mention this on the second page of the section "Core Animation Basics":
The data and state information of a layer object is decoupled from the visual presentation of that layer’s content onscreen.
Animations happen on a separate layer called the presentation layer which is what you see on screen. If you print out the values of the animated property during the animation they don't change at all.
During the animation you have a difference between the presentation and the model but the animation is probably short and that difference will go away as soon as the animation finishes, so it doesn't really matter... unless the animation doesn't go away. Then the difference has persisted and now you have to deal with having out-of-sync data in two places.
How not to solve this issue
One could say that everything looks good, it's just that hit testing is wrong. Let's patch hit testing! Change the hit testing to use the segment control's layer's presentation layer and hit testing will work. Great, right? This would be like putting one plaster on top of another instead of solving the problem at its origin.
How to get rid of the side effects
It's really simple: don't use those lines and remove animations when they are finished. One might say that this technique (not removing an animation) has been used by Apple in slides and sample code so it's what Apple recommends, but there is a subtle detail that is easily missed:
When Apple introduced Core Animation at WWDC 2007 they used this technique to animate layers being removed (for example, fading out). Quote below is from Session 211 - Adding Core Animation to Your Application:
To animate layer removal, use animations with
fillMode = forwards, removedOnCompletion = NO
Animation delegate can remove the layer
In this case it's perfectly fine to not remove the animation since it could cause the layer to jump back to it's original size, opacity, etc. for one frame before being removed from the layer hierarchy. As they said in the above slide: the "animation delegate can remove the layer" (that is: do the clean up).
In other cases no one does the clean up and you are left with the mess of having out of sync data in two places (as mentioned above).
The solution is a different approach to the animation
When building animations, I try to think of like this:
If your animation mysteriously went away, the model value should be the expected end state.
Applied to your example, the segmented control is moving towards it's final position to stop there and stay there. In that case the actual position of the segmented control should be the end position. The application should work even if the animation isn't there.
So what about the animation? Instead of animating from 0 offset to some offset, you reverse that and animate from some negative offset to 0 offset.
CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
[startAnimation setFromValue:#(-slidingUpValue)]]; // these two changed
[startAnimation setToValue:#(0.0)]; // places with each other
[startAnimation setDuration:1.0];
// no fill mode
// animation is removed on completion
[startAnimation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:0.0 :0.0 :0.3 :1.8]];
[[gameTypeControl layer] addAnimation:startAnimation forKey:#"Move in game type control from bottom"]; // I like adding descriptive keys for easier debugging

Is it possible to control a CAAnimation's timeline?

I have a complex animation that usually runs just by itself, driven by a certain CAMediaTimingFunction. This works fine.
Now, I want to control that same animation's time(line) using an external value, for example from a slider or a gesture recognizer. In other words, I don't want to have the "clock" drive the timeline, but a slider, so one can scrub back and forth with it and "freeze" the animation by putting the slider to a certain value.
Is this possible? If so, how?
It's possible (and quite easy), but I only tried this as an experiment (for a complex animation driven by a pinch gesture recognizer), so I'd love to hear if this solution is sufficient:
You need to set the speed of your animation to 0 and the time offset to the point in time you want to jump to, e.g.
CABasicAnimation* animation = [CABasicAnimation ...];
animation.speed = 0;
animation.duration = 1;
animation.timeOffset = 0.5;
will make the animation jump to the point it would be after half a second.
Now, you can't manipulate CAAnimation objects after they've been added to the layer, so you'll need to add a new animation every time the offset changes (and remove the old one, don't forget ;).

Resources