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:
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":
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:
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:
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.