Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
376 views
in Technique[技术] by (71.8m points)

ios - How UIView animations with blocks work under the hood

I'm new to Objective-C/iOS programming and I'm trying to understand how UIView animation works under the hood.

Say I have a code like this:

[UIView animateWithDuration:2.0 animations:^{
    self.label.alpha = 1.0;
}];

The thing that gets passed as an animations argument is an Objective-C block (something like lambdas/anonymous functions in other languages) that can be executed and then it changes the alpha property of label from current value to 1.0.

However, the block does not accept an animation progress argument (say going from 0.0 to 1.0 or from 0 to 1000). My question is how the animation framework uses this block to know about intermediate frames, as the block only specifies the final state.

EDIT: My questions is rather about under the hood operation of animateWithDuration method rather than the ways to use it.

My hypothesis of how animateWithDuration code works is as follows:

  1. animateWithDuration activates some kind of special state for all view objects in which changes are not actually performed but only registered.
  2. it executes the block and the changes are registered.
  3. it queries the views objects for changed state and gets back the initial and target values and hence knows what properties to change and in what range to perform the changes.
  4. it calculates the intermediate frames, based on the duration and initial/target values, and fires the animation.

Can somebody shed some light on whether animateWithDuration really works in such way?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Of course I don't know what exactly happens under the hood because UIKit isn't open-source and I don't work at Apple, but here are some ideas:

Before the block-based UIView animation methods were introduced, animating views looked like this, and those methods are actually still available:

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
myView.center = CGPointMake(300, 300);
[UIView commitAnimations];

Knowing this, we could implement our own block-based animation method like this:

+ (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:duration];
    animations();
    [UIView commitAnimations];
}

...which would do exactly the same as the existing animateWithDuration:animations: method.

Taking the block out of the equation, it becomes clear that there has to be some sort of global animation state that UIView then uses to animate changes to its (animatable) properties when they're done within an animation block. This has to be some sort of stack, because you can have nested animation blocks.

The actual animation is performed by Core Animation, which works at the layer level – each UIView has a backing CALayer instance that is responsible for animations and compositing, while the view mostly just handles touch events and coordinate system conversions.

I won't go into detail here on how Core Animation works, you might want to read the Core Animation Programming Guide for that. Essentially, it's a system to animate changes in a layer tree, without explicitly calculating every keyframe (and it's actually fairly difficult to get intermediate values out of Core Animation, you usually just specify from and to values, durations, etc. and let the system take care of the details).

Because UIView is based on a CALayer, many of its properties are actually implemented in the underlying layer. For example, when you set or get view.center, that is the same as view.layer.location and changing either of these will also change the other.

Layers can be explicitly animated with CAAnimation (which is an abstract class that has a number of concrete implementations, like CABasicAnimation for simple things and CAKeyframeAnimation for more complex stuff).

So what might a UIView property setter do to accomplish "magically" animating changes within an animation block? Let's see if we can re-implement one of them, for simplicity's sake, let's use setCenter:.

First, here's a modified version of the my_animateWithDuration:animations: method from above that uses the global CATransaction, so that we can find out in our setCenter: method how long the animation is supposed to take:

- (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];

    animations();

    [CATransaction commit];
}

Note that we don't use beginAnimations:... and commitAnimations anymore, so without doing anything else, nothing will be animated.

Now, let's override setCenter: in a UIView subclass:

@interface MyView : UIView
@end

@implementation MyView
- (void)setCenter:(CGPoint)position
{
    if ([CATransaction animationDuration] > 0) {
        CALayer *layer = self.layer;
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
        animation.fromValue = [layer valueForKey:@"position"];
        animation.toValue = [NSValue valueWithCGPoint:position];
        layer.position = position;
        [layer addAnimation:animation forKey:@"position"];
    }
}
@end

Here, we set up an explicit animation using Core Animation that animates the underlying layer's location property. The animation's duration will automatically be taken from the CATransaction. Let's try it out:

MyView *myView = [[MyView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
myView.backgroundColor = [UIColor redColor];
[self.view addSubview:myView];

[self my_animateWithDuration:4.0 animations:^{
    NSLog(@"center before: %@", NSStringFromCGPoint(myView.center));
    myView.center = CGPointMake(300, 300);
    NSLog(@"center after : %@", NSStringFromCGPoint(myView.center));
}];

I'm not saying that this is exactly how the UIView animation system works, it's just to show how it could work in principle.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...