iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (77 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
9.72Mb size Format: txt, pdf, ePub
Spinning with CABasicAnimation

In this section, you are going to use an animation object to spin the implicit layer of the time field in
HypnoTime
’s
TimeViewController
whenever it is updated (
Figure 23.5
). (Recall that an implicit layer is a layer created by a view when the view is instantiated. The time field is a
UILabel
, which is a subclass of
UIView
, so it has an implicit layer that we can animate.)

 

Figure 23.5  Current time mid-spin

 

Open
HypnoTime.xcodeproj
.

 

The Core Animation code you will write in this exercise will be in
TimeViewController.m
. So import the header from the QuartzCore framework at the top of this file.

 
#import
@implementation TimeViewController

In order to spin the
timeLabel
, you need an animation object that will apply a 360-degree rotation over time to a layer. So we need to determine four things:

 
  • Which type of animation object suits this purpose?
 
  • What key path handles rotation?
 
  • How long should the animation take to complete?
 
  • What values should the animation interpolate?
 

To answer the first question, think about the number of keyframes an animation would need to make a complete revolution. It only needs two: a non-rotated value and a fully-rotated value, so
CABasicAnimation
can handle this task.

 

To determine the key path, we use the property of
CALayer
that deals with rotation. This property is its
transform
, the transformation matrix that is applied to the layer when it draws. The
transform
of a layer can rotate, scale, translate, and skew its frame. (For more details, go to Core Animation Extensions To Key-Value Coding in the documentation.) This exercise only calls for rotating the layer, and, fortunately, you can isolate the rotation of the
transform
in a key path (
Figure 23.6
). Therefore, the key path of the basic animation will be
transform.rotation
.

 

Figure 23.6  Core Animation Extensions to Key-Value Coding documentation

 

Let’s make the duration of this animation one second. That’s enough time for the user to see the spin but not so much time that they get bored waiting for it to complete.

 

Lastly, we need the values of the two keyframes: the
fromValue
and the
toValue
. The documentation says that the
transform.rotation
is in radians, so that’s how we’ll pass these values. A little geometry research tells us that no rotation is
0
radians and a full rotation is
2 * PI
radians. When using a
CABasicAnimation
, if you do not supply a
fromValue
, the animation assumes that
fromValue
is the current value of that property. The default value of the
transform
property is the identity matrix – no rotation. This means you only have to supply the final keyframe to this animation object.

 

In
TimeViewController.h
, declare a new method.

 
- (void)spinTimeLabel;
 

In
TimeViewController.m
, edit the method
showCurrentTime:
to call this method.

 
- (IBAction)showCurrentTime:(id)sender
{
    NSDate *now = [NSDate date];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setTimeStyle:NSDateFormatterMediumStyle];
    [timeLabel setText:[formatter stringFromDate:now]];
    [self spinTimeLabel];
}
 

In
TimeViewController.m
, create an animation object that will spin a layer around in one second.

 
- (void)spinTimeLabel
{
    // Create a basic animation
    CABasicAnimation *spin =
                [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    // fromValue is implied
    [spin setToValue:[NSNumber numberWithFloat:M_PI * 2.0]];
    [spin setDuration:1.0];
}
 

Now that you have an animation object, it needs to be applied to a layer for it to have any effect.
CALayer
instances implement the method
addAnimation:forKey:
for this purpose. This method takes two arguments: an animation object and a key. Once again: this key is
not
the key path; it is simply a human-readable name for this animation.

 

In
spinTimeLabel
, add your animation object to
timeLabel
’s layer to start the animation.

 
    [spin setToValue:[NSNumber numberWithFloat:M_PI * 2.0]];
    [spin setDuration:1.0];
    // Kick off the animation by adding it to the layer
    [[timeLabel layer] addAnimation:spin
                             forKey:@"spinAnimation"];
}
 

Build and run the application. The label field will spin 360 degrees when the user updates it – either by switching to the
Time
tab or tapping the button.

 

Note that the animation object exists independently of the layer it is applied to. This animation object could be added to any layer to rotate it 360 degrees. You can create animation objects and keep them around for later use in the application.

 
Timing functions

You may notice that the rotation of the label field’s layer starts and stops suddenly; it would look nicer if it gradually accelerated and decelerated. This sort of behavior is controlled by the animation’s timing function, which is an instance of the class
CAMediaTimingFunction
. By default, the timing function is linear – the values are interpolated linearly. Changing the timing function changes how these animations are interpolated. It doesn’t change the duration or the keyframes.

 

In
spinTimeLabel
, change the timing function to

ease

in and out of the animation.

 
    [spin setDuration:1.0];
    
// Set the timing function
    CAMediaTimingFunction *tf =  [CAMediaTimingFunction
                        functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [spin setTimingFunction:tf];
    [[timeLabel layer] addAnimation:spin
                             forKey:@"spinAnimation"];
}

Build and run the application and see how the animation changes.

 

There are four timing functions, you have seen linear and
kCAMediaTimingFunctionEaseInEaseOut
. The other two are
kCAMediaTimingFunctionEaseIn
(accelerates gradually, stops suddenly) and
kCAMediaTimingFunctionEaseOut
(accelerates suddenly, stops slowly). You can also create your own timing functions with
CAMediaTimingFunction
. See the doc for details.

 
Animation completion

Sometimes you want to know when an animation is finished. For instance, you might want to chain animations or update another object when an animation completes. How can you know when an animation is complete? Every animation object can have a delegate, and the animation object sends the message
animationDidStop:finished:
to its delegate when an animation stops.

 

Edit
TimeViewController.m
so that it logs a message to the console whenever an animation stops.

 
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    NSLog(@"%@ finished: %d", anim, flag);
}
- (void)spinTimeLabel
{
    // Create a basic animation
    CABasicAnimation *spin =
                [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    
[spin setDelegate:self];
    // fromValue is implied
    [spin setToValue:[NSNumber numberWithFloat:M_PI * 2.0]];
    [spin setDuration:1.0];
    // Set the timing function
    CAMediaTimingFunction *tf =  [CAMediaTimingFunction
                        functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [spin setTimingFunction:tf];
    // Kick off the animation by adding it to the layer
    [[timeLabel layer] addAnimation:spin
                             forKey:@"spinAnimation"];
}
 

Build and run the application. Notice the log statements when the animation is complete. If you press the button several times quickly, the animation in progress will be interrupted by a new one. The interrupted animation will still send the message
animationDidStop:finished:
to its delegate; however, the finished flag will be
NO
.

 
Bouncing with a CAKeyframeAnimation

For practice with
CAKeyframeAnimation
, you are going to make the time label grow and shrink to give it a bouncing effect (
Figure 23.7
).

 

Figure 23.7  Current time mid-bounce

 
 

Declare a new method in
TimeViewController.h
.

 
- (void)bounceTimeLabel;
 

In
TimeViewController.m
, replace the code that spins the label in
showCurrentTime:
with a call to
bounceTimeLabel
.

 
- (IBAction)showCurrentTime:(id)sender
{
    NSDate *now = [NSDate date];
    static NSDateFormatter *formatter = nil;
    if (!formatter) {
        formatter = [[NSDateFormatter alloc] init];
        [formatter setDateStyle:NSDateFormatterMediumStyle];
    }
    [timeLabel setText:[formatter stringFromDate:now]];
    
[self spinTimeLabel];
    
[self bounceTimeLabel];
}
 

Then, implement
bounceTimeLabel
in
TimeViewController.m
.

 
- (void)bounceTimeLabel
{
    // Create a key frame animation
    CAKeyframeAnimation *bounce =
                    [CAKeyframeAnimation animationWithKeyPath:@"transform"];
    // Create the values it will pass through
    CATransform3D forward = CATransform3DMakeScale(1.3, 1.3, 1);
    CATransform3D back = CATransform3DMakeScale(0.7, 0.7, 1);
    CATransform3D forward2 = CATransform3DMakeScale(1.2, 1.2, 1);
    CATransform3D back2 = CATransform3DMakeScale(0.9, 0.9, 1);
    [bounce setValues:[NSArray arrayWithObjects:
                        [NSValue valueWithCATransform3D:CATransform3DIdentity],
                        [NSValue valueWithCATransform3D:forward],
                        [NSValue valueWithCATransform3D:back],
                        [NSValue valueWithCATransform3D:forward2],
                        [NSValue valueWithCATransform3D:back2],
                        [NSValue valueWithCATransform3D:CATransform3DIdentity],
                        nil]];
    // Set the duration
    [bounce setDuration:0.6];
    // Animate the layer
    [[timeLabel layer] addAnimation:bounce
                             forKey:@"bounceAnimation"];
}

Build and run the application. The time field should now scale up and down and up and down when it is updated. The constant
CATransform3DIdentity
is the
identity matrix
. When the transform of a layer is the identity matrix, no scaling, rotation, or translation is applied to the layer: it sits squarely within its bounding box at its position. So, this animation starts at no transformation, scales a few times, and then reverts back to no transformation.

 

Once you understand layers and the basics of animation, there isn’t a whole lot to it – other than finding the appropriate key path and getting the timing of things right. Core Animation is one of those things you can play around with and see results immediately. So play with it!

 

Other books

I Can Touch the Bottom by Ms. Michel Moore
Running From Mercy by Terra Little
Ace's Basement by Ted Staunton
Elf Sight by Avril Sabine
Revenant by Patti Larsen