Cocoa and Objective-C: Animating CALayers

Exclusive offer: get 50% off this eBook here
Cocoa and Objective-C Cookbook

Cocoa and Objective-C Cookbook — Save 50%

Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development

$23.99    $12.00
by Jeff Hawkins | May 2011 | Cookbooks

Layers are a very powerful way to draw or animate your views. Using layer-backed views is similar to using layers in a graphics application like Adobe's Photoshop. Layers can be stacked and animated to create powerful effects.

In this article by Jeff Hawkins, author of Cocoa and Objective-C Cookbook, we will cover:

  • Understanding the CALayer class
  • Animation by changing properties
  • Using animation to swap views
  • Using the flip animation
  • Using a CAAnimationGroup
  • Using Keyframe animations
  • Using CAMediaTiming in animations

 

Cocoa and Objective-C Cookbook

Cocoa and Objective-C Cookbook

Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development

        Read more about this book      

(For more resources related to this subject, see here.)

Some recipes in this article require Mac OS X Snow Leopard 10.6.

Understanding the CALayer class

In this recipe, we will use multiple layers to draw our custom view. Using a delegate method, we will draw the background of our view. A second layer will be used to include an image centered in the view. When complete, the sample application will resemble the following screenshot:

Cocoa and Objective-C: Animating CALayers

Getting ready

In Xcode, create a new Cocoa Application and name it CALayer.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. In the Xcode project, expand the Frameworks group and then expand Other Frameworks. Right-click on Other Frameworks and choose Add…, then choose Existing Frameworks… Find QuartzCore.framework in the list of frameworks and choose Add.
  3. Click on MyView.m to open it and add the following import:

    #import <QuartzCore/QuartzCore.h>

  4. Remove the initWithFrame: and drawRect: methods.
  5. Add the following awakeFromNib method:

    - (void) awakeFromNib {
    CALayer *largeLayer = [CALayer layer];
    [largeLayer setName:@"large"];
    [largeLayer setDelegate:self];
    [largeLayer setBounds:[self bounds]];
    [largeLayer setBorderWidth:4.0];
    [largeLayer setLayoutManager:[CAConstraintLayoutManager
    layoutManager]];

    CALayer *smallLayer = [CALayer layer];
    [smallLayer setName:@"small"];
    CGImageRef image = [self convertImage:[NSImage
    imageNamed:@"LearningJQuery"]];
    [smallLayer setBounds:CGRectMake(0, 0, CGImageGetWidth(image),
    CGImageGetHeight(image))];
    [smallLayer setContents:(id)image];
    [smallLayer addConstraint:[CAConstraint
    constraintWithAttribute:kCAConstraintMidY
    relativeTo:@"superlayer"
    attribute:kCAConstraintMidY]];

    [smallLayer addConstraint:[CAConstraint
    constraintWithAttribute:kCAConstraintMidX
    relativeTo:@"superlayer"
    attribute:kCAConstraintMidX]];

    CFRelease(image);

    [largeLayer addSublayer:smallLayer];
    [largeLayer setNeedsDisplay];

    [self setLayer:largeLayer];
    [self setWantsLayer:YES];
    }

  6. Add the following two methods to the MyView class as well:

    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
    {

    CGContextSetRGBFillColor(context, .5, .5, .5, 1);
    CGContextFillRect(context, [layer bounds]);
    }

    - (CGImageRef) convertImage:(NSImage *)image {
    CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef
    )[image TIFFRepresentation],
    NULL);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,
    NULL);
    CFRelease(source);

    return imageRef;
    }

  7. Open the MyView.h file and add the following method declaration:

    - (CGImageRef) convertImage:(NSImage *)image;

  8. Right-click on the CALayer project in Xcode's project view and choose Add…, then choose Existing Files…. Choose the LearningJQuery.jpg image and click on Add.
  9. Double-click on the MainMenu.xib file in the Xcode project. From Interface Builder's Library palette, drag a Custom View into the application window.
  10. From Interface Builder's Inspectors palette, select the Identity tab and set the Class popup to MyView.
  11. Back in Xcode, choose Build and Run from the toolbar to run the application.

How it works...

We are creating two layers for our view. The first layer is a large layer, which will be the same size as our MyView view. We set the large layers delegate to self so that we can draw the layer in the drawLayer: delegate method. The drawLayer: delegate method simply fills the layer with a mid-gray color. Next, we set the bounds property and a border width property on the larger layer.

Next, we create a smaller layer whose contents will be the image that we included in the project. We also add a layout manager to this layer and configure the constraints of the layout manager to keep the smaller layer centered both horizontally and vertically relative to the larger view using the superlayer keyword.

Lastly, we set the small layer as a sub-layer of the large layer and force a redraw of the large layer by calling setNeedsDisplay. Next, we set the large layer as the MyView's layer. We also need to call the setWantsLayer:YES on the MyView to enable the use of layers in our view.

There's more...

Since we used a layout manager to center the image in the view, the layout manager will also handle the centering of the image when the user resizes the view or window. To see this in action, modify the Size properties in Interface Builder for the custom view as shown in the screenshot below:

Cocoa and Objective-C: Animating CALayers

Animation by changing properties

Cocoa provides a way to animate views by changing properties using implied animations. In this recipe, we will resize our custom view when the resize button is clicked, by changing the views frame size.

Getting ready

In Xcode, create a new Cocoa Application and name it ChangingProperties.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView from the Subclass of popup. Name the new file MyView.m.
  2. Click on the MyView.m file to open it and add the following in the drawRect: method:

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[
    self bounds] xRadius:8.0 yRadius:8.0];
    [path setClip];

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect:[self bounds]];

    [path setLineWidth:3.0];

    [[NSColor grayColor] setStroke];
    [path stroke];

  3. Click on the ChangingPropertiesAppDelegate.h to open it.
  4. Next, insert an import for the MyView.h header file:

    #import "MyView.h"

  5. Add the following variables to the class interface:

    NSButton *button;
    MyView *myView;

  6. Add the following properties to the class interface:

    @property (assign) IBOutlet NSButton *button;
    @property (assign) IBOutlet MyView *myView;

  7. Add the method declaration for when the Resize button is clicked:

    - (IBAction) resizeButtonHit:(id)sender;

  8. Click on the ChangingPropertiesAppDelegate.m file to open it.
  9. Add our synthesized variables below the synthesized window variable:

    @synthesize button;
    @synthesize myView;

  10. Create a global static boolean for tracking the size of the view:

    static BOOL isSmall = YES;

  11. Add the resizeButtonHit: method to the class implementation:

    - (IBAction) resizeButtonHit:(id)sender {
    NSRect small = NSMakeRect(20, 250, 150, 90);
    NSRect large = NSMakeRect(20, 100, 440, 240);

    if (isSmall == YES) {
    [[myView animator] setFrame:large];
    isSmall = NO;
    } else {
    [[myView animator] setFrame:small];
    isSmall = YES;
    }
    }

  12. Double-click on the MainMenu.xib file in the Xcode project. From Interface Builders Library palette, drag a Custom View into the application window.
  13. From Interface Builder's Inspector's palette, select the Identity tab and set the Class popup to MyView.
  14. From the Library palette, drag a Push Button into the application window.
  15. Adjust the layout of the Custom View and button so that it resembles the screenshot below:

    Cocoa and Objective-C: Animating CALayers

  16. From Interface Builder's Inspector's palette, select the Identity tab and set the Class popup to MyView.
  17. Right-click on the Changing Properties App Delegate so that you can connect the outlets to the MyView Custom View, the Resize Push Button, and the resizeButtonHit action:

    Cocoa and Objective-C: Animating CALayers

  18. Back in Xcode, choose Build and Run from the toolbar to run the application.

How it works...

We define two sizes for our view, one small size that is the same as the initial size of the view, and one large size. Depending on the state of the isSmall global variable, we set the view's frame size to one of our predefined sizes. Note that we set the view's frame via the views animator property. By using this property, we make use of the implicit animations available in the view.

There's more...

Using the same technique, we can animate several other properties of the view such as its position or opacity. For more information on which properties can be implicitly animated, see Apple's Core Animation Programming Guide.

Cocoa and Objective-C Cookbook Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development
Published: May 2011
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources related to this subject, see here.)

Using animation to swap views

One use of animation in Cocoa applications is to change views. In this recipe, we will create two views and swap them using a transition to slide the new view in from the left, replacing the existing view.

Getting ready

In Xcode, create a new Cocoa Application and name it Swap.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. In the project view, expand the Frameworks group and then expand Other Frameworks. Right-click on Other Frameworks and choose Add…, then choose Existing Frameworks… Find QuartzCore.framework in the list of frameworks and choose Add.
  3. Click on the SwapAppDelegate.h file to open it and add the following import:

    #import "MyView.h"

  4. Add the following variables to the SwapAppDelegate class interface after the window variable:

    NSButton *button;
    MyView *myView;
    NSView *nameView;
    NSView *emailView;
    NSString *currentView;

  5. Add the following properties below the property for the window variable:

    @property (retain, nonatomic) IBOutlet NSButton *button;
    @property (retain, nonatomic) IBOutlet MyView *myView;
    @property (retain, nonatomic) IBOutlet NSView *nameView;
    @property (retain, nonatomic) IBOutlet NSView *emailView;

  6. We need to add the following method declaration to the class interface:

    - (IBAction) swapButtonHit:(id)sender;

  7. Open the SwapAppDelegate.m file and add an import for QuartzCore:

    #import <QuartzCore/QuartzCore.h>

  8. We need to synthesize the variables that we added to the class interface. Below the window variable's synthesize statement, add the following:

    @synthesize button;
    @synthesize myView;
    @synthesize nameView;
    @synthesize emailView;

  9. In the applicationDidFinishLaunching method add the following code:

    currentView = @"nameView";
    [myView addSubview:nameView];

  10. Next, add the swapButtonHit: method:

    - (IBAction) swapButtonHit:(id)sender {
    if ([currentView isEqualToString:@"nameView"] == YES) {
    [[myView animator] replaceSubview:nameView with:emailView];
    currentView = @"emailView";
    } else {
    [[myView animator] replaceSubview:emailView with:nameView];
    currentView = @"nameView";
    }
    }

  11. Click on the MyView.m file to open it and add the following imports:

    #import "MyView.h"
    #import <QuartzCore/QuartzCore.h>

  12. Add the following awakeFromNib method:

    - (void) awakeFromNib {
    [self setWantsLayer:YES];

    CATransition *transition = [CATransition animation];
    [transition setDuration:1.0];
    [transition setType:kCATransitionPush];
    [transition setSubtype:kCATransitionFromLeft];

    NSDictionary *dictionary = [NSDictionary
    dictionaryWithObject:transition forKey:@"subviews"];
    [self setAnimations:dictionary];
    }

  13. Double-click on the MainMenu.xib file to open it in Interface Builder.
  14. From Interface Builders library palette, drag a Custom View into the application window.
  15. Using the Inspectors palette, choose the Identity tab and set the Class to MyView.
  16. From the Inspectors palette, choose the Size tab to resize the view. Make the view 480 pixels in width and 360 pixels in height.
  17. You will also need to drag a Push Button into the window.
  18. Your layout should resemble the following screenshot:

    Cocoa and Objective-C: Animating CALayers

  19. Next, we need to add two Custom Views from the Library palette to the MainMenu.xib project window.
  20. Rename the first view from Custom View to Name.
  21. Change the second view's name to Email.
  22. Your MainMenu.xib project window should resemble the following:

    Cocoa and Objective-C: Animating CALayers

  23. Double-click on the Name view that you just added to open it.
  24. Using the Size tab on the Inspector palette, set the view's size to 480 pixels in width and 280 pixels in height.
  25. From the Library palette, drag some labels and text fields into the view so that it resembles the following screenshot:

    Cocoa and Objective-C: Animating CALayers

  26. Double-click on the Email view that you just added to open it.
  27. Using the Size tab on the Inspector palette, set the views size to 480 pixels in width and 280 pixels in height.
  28. From the Library palette, drag some labels and text fields into the view so that it resembles the following screenshot:

    Cocoa and Objective-C: Animating CALayers

  29. From the MainMenu.xib project window, double-click on the Window (Swap) item to open it.
  30. Right-click on the Swap App Delegate to show the outlet connections window:

    Cocoa and Objective-C: Animating CALayers

  31. Drag a connection from the button outlet to the Push Button in the application window.
  32. Drag a connection from the emailView outlet to the Email NSView in the MainMenu.xib project window.
  33. Drag a connection from the myView outlet to the MyView view in the application window.
  34. Drag a connection from the nameView outlet to the Name NSView in the MainMenu.xib project window.
  35. Back in Xcode, choose Build and Run from the toolbar to run the application.

How it works...

First, we set up our MyView as a placeholder for where the views will be swapped. The MyView class sets up a CATransition in its awakeFromNib method using a kCATransitionPush type and a kCATransitionFromLeft subtype. Also note that we call setWantsLayer:YES to enable layers in the MyView class.

Next, we create a dictionary of animations containing our transition animation. We then set the dictionary as the view's animations.

Finally, in the SwapAppDelegate class's swapButtonHit: method, we use the currentView variable to determine which view is currently displaying. We then call myView's replaceSubview method using the animator property, which sets our animation in action.

There's more...

The CATransition animation supports four types and subtypes that can be used to alter the transition effect. The four supported types are kCATransitionFade, kCATransitionMoveIn, kCATransitionPush, and kCATransitionReveal. The following subtypes are supported: kCATransitionFromRight, kCATransitionFromLeft, kCATransitionFromTop, and kCATransitionFromBottom.

Using the flip animation

In this recipe, we will use the transform property to animate the flipping of a views layer.

Getting ready

In Xcode, create a new Cocoa Application and name it Flip.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class,then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. Right-click on the Flip project icon in Xcode's project view. Choose Add…, then choose Existing Files…. Navigate to /Library/Desktop Pictures/Aqua Graphite.jpg and choose Add.
  3. Click on MyView.m to open the MyView implementation.
  4. You can safely remove the initWithFrame: and drawRect: methods.
  5. Add the following awakeFromNib method:

    - (void) awakeFromNib {
    CALayer *layer = [CALayer layer];

    CGImageRef image = [self convertImage:[NSImage imageNamed:@"
    Aqua Graphite"]];

    layer.anchorPoint = CGPointMake(0.5, 0.5);
    layer.contents = (id)image;
    layer.borderWidth = 1.0;

    CFRelease(image);

    [self setLayer:layer];
    [self setWantsLayer:YES];
    }

  6. We need to add the following method to convert our NSImage to a CGImageRef:

    - (CGImageRef) convertImage:(NSImage *)image {
    CGImageSourceRef source = CGImageSourceCreateWithData((CFData
    Ref)[image TIFFRepresentation],
    NULL);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source,
    0, NULL);
    CFRelease(source);

    return imageRef;
    }

  7. Click on MyView.h to open the MyView class interface.
  8. Add the following method declaration for our convertImage method:

    - (CGImageRef) convertImage:(NSImage *)image;

  9. In the project view, expand the Frameworks group and then expand Other Frameworks. Right-click on Other Frameworks and choose Add…, then choose Existing Frameworks… Find QuartzCore.framework in the list of frameworks and choose Add.
  10. Click on the FlipAppDelegate.h file to open the file.
  11. Add an import for the MyView class:

    #import "MyView.h"

  12. Next, we need to add a few variables to the MyView class. Add the following variables below the window variable:

    MyView *myView;
    NSButton *button;
    BOOL forward;

  13. Add the following properties for the myView and button variables:

    @property (assign) IBOutlet MyView *myView;
    @property (assign) IBOutlet NSButton *button;

  14. We need to add two method declarations for the methods in the MyView class implementation:

    - (IBAction) performFlip:(id)sender;
    CGFloat DegreesToRadians(CGFloat degrees);

  15. Click on FlipAppDelegate.m to open the file.
  16. Add an import for QuartzCore:

    #import <QuartzCore/QuartzCore.h>

  17. Add a synthesize statement for the myView and button variables:

    @synthesize myView;
    @synthesize button;

  18. Next, we need to add the performFlip: method:

    - (IBAction) performFlip:(id)sender {
    CALayer *layer = [myView layer];

    [CATransaction setValue:[NSNumber numberWithFloat:2.0f]
    forKey:kCATransactionAnimationDuration];

    if (forward == NO) {
    layer.transform =
    CATransform3DMakeRotation(DegreesToRadians(180), 0.0f,
    1.0f, 0.0f);
    } else {
    layer.transform =
    CATransform3DMakeRotation(DegreesToRadians(0), 0.0f, 1.0f,
    0.0f);
    }

    forward = forward == NO ? YES : NO;
    }

  19. Add our utility method to convert degrees to radians:

    CGFloat DegreesToRadians(CGFloat degrees)
    {
    return degrees * M_PI / 180;
    }

  20. Double-click on MainMenu.xib to open the file in Interface Builder.
  21. From the Library palette in Interface Builder, drag a Custom View to the application window.
  22. Using the Size tab in the Inspector palette set the size of the custom view to 440 pixels in width and 192 pixels in height.
  23. Select the Identity tab in the Inspector palette and set the Class to MyView
  24. Drag a Push Button from the Library palette to the application window. Change its title to Flip.
  25. Your layout should resemble the screenshot below:

    Cocoa and Objective-C: Animating CALayers

  26. From the MainMenu.xib project window, right-click on the Flip App Delegate to connect the outlets and actions.
  27. Drag a connection from the button outlet to the Push Button.
  28. Drag a connection from the myView outlet to the MyView Custom View.
  29. Finally, drag a connection from the performFlip: action to the Push Button.
  30. When finished, your connections should look similar to those shown below:

    Cocoa and Objective-C: Animating CALayers

  31. Back in Xcode, choose Build and Run to run the application.

How it works...

In the awakeFromNib method of the MyView class, we create a layer whose contents will contain the image that we added to the project from the Desktop Pictures folder. Note that we need to convert the NSImage to a CGImageRef, which the contents property of the layer will be expecting. We also set the layers anchor point to the center of the layer and add a one point black border around the layer. Next, we set the layer as the MyView's layer and we call setWantsLayer:YES on the view.

When the Flip button is clicked, it invokes our performFlip: method in the FlipAppDelegate. Here we get the view's layer and set the transform property to a CATransform3DMakeRotation transform. Because the transform expects radians, we use our utility method to convert degrees to radians. Since we are rotating the y axis, we set the x and z axis to 0.0 and the y axis to 1.0.

There's more...

The default duration for animations is one second. This seems to be a little fast for the flip animation. In the performFlip: method, we adjust the duration to two seconds using the kCATransactionAnimationDuration property.

Cocoa and Objective-C Cookbook Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development
Published: May 2011
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources related to this subject, see here.)

Using a CAAnimationGroup

In this recipe, we will group multiple animations together and run them on our view's layer.

Getting ready

In Xcode, create a new Cocoa Application and name it AnimationGroup.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. Click on MyView.m to open the file.
  3. Add the following awakeFromNib method:

    - (void) awakeFromNib {
    [self setWantsLayer:YES];
    }

  4. In the drawRect: method, add the following code:

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[
    self bounds] xRadius:8.0 yRadius:8.0];
    [path setClip];

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect:[self bounds]];

    [path setLineWidth:3.0];

    [[NSColor grayColor] setStroke];
    [path stroke];

  5. Click on the AnimationGroupAppDelegate.h to open it and add an import for the MyView class header file:

    #import "MyView.h"

  6. Add the following variables to the delegate's class interface:

    NSButton *button;
    MyView *myView;

  7. We need to add some properties for the new variables. Add these properties below the NSWindow property:

    @property (assign) IBOutlet NSButton *button;
    @property (assign) IBOutlet MyView *myView;

  8. Finally, add the method declaration for the buttonHit: method:

    - (IBAction) buttonHit:(id)sender;

  9. Click on the AnimationGroupAppDelegate.m to open it.
  10. Add an import for QuartzCore:

    #import <QuartzCore/QuartzCore.h>

  11. In the project view, expand the Frameworks group and then expand Other Frameworks. Right-click on Other Frameworks and choose Add…, then choose Existing Frameworks… Find QuartzCore.framework in the list of frameworks and choose Add.
  12. Synthesize the button and myView variables:

    @synthesize button;
    @synthesize myView;

  13. Finally, add the following buttonHit: method:

    - (IBAction) buttonHit:(id)sender {
    CABasicAnimation *fadeAnimation = [CABasicAnimation
    animationWithKeyPath:@"opacity"];
    [fadeAnimation setAutoreverses:YES];
    [fadeAnimation setToValue:[NSNumber numberWithFloat:0.0]];

    CABasicAnimation *scaleAnimation = [CABasicAnimation
    animationWithKeyPath:@"transform.scale"];
    [scaleAnimation setAutoreverses:YES];
    [scaleAnimation setToValue:[NSNumber numberWithFloat:0.0]];

    CATransform3D transform = CATransform3DMakeRotation(M_PI, 0.0f,
    1.0f, 0.0f);
    CABasicAnimation *rotateAnimation = [CABasicAnimation
    animationWithKeyPath:@"transform.rotation"];
    [rotateAnimation setToValue:[NSValue
    valueWithCATransform3D:transform]];

    NSArray *animations = [NSArray arrayWithObjects:fadeAnimation,
    scaleAnimation, rotateAnimation, nil];

    CAAnimationGroup *animationGroup = [CAAnimationGroup
    animation]; [animationGroup setDuration:5.0];
    [animationGroup setRepeatCount: 1e100f];
    [animationGroup setAnimations:animations];

    [[myView layer] addAnimation:animationGroup forKey:@"fadeAnd
    Rotate"];
    }

  14. Double-click on the MainMenu.xib file to open it in Interface Builder.
  15. From the Library palette, drag a Custom View into the application window.
  16. Using the Identity tab on the Inspector palette, set the Custom View's Class to MyView.
  17. We will need a button to start the animation, so drag a Push Button into the application window. Set the buttons title to Animate.
  18. The final application window layout should look similar to the following screenshot:

    Cocoa and Objective-C: Animating CALayers

  19. Next, we need to connect our outlets and actions. Right-click on the Animation Group App Delegate from the MainMenu.xib project window to display the outlets window:

    Cocoa and Objective-C: Animating CALayers

  20. Drag a connection from the button outlet to the "Animate" Push Button in the application window.
  21. Drag a connection from the myView outlet to the MyView Custom View.
  22. Finally, drag a connection from the buttonHit: action to the "Animate" Push Button in the application window.
  23. Back in Xcode, choose Build and Run to run the application.

How it works...

As we have done in other recipes, we create a simple custom view, which we will animate. When the buttonHit: method is called, we create two basic animations. In the first animation, we adjust the opacity of the view's layer from its current value to 0.0. The second animation will modify the view layer's scale to a value of 0.0. Finally, our last animation will rotate the view layer.

In order to group the animations, we add them to an NSArray so we can later add them to the CAAnimationGroup. Next, we create the CAAnimationGroup, set its duration to five seconds, and set it to repeat forever. Finally, we set our array of animations and add our animation group to the view's layer, which starts the animation.

There's more...

When animations are added to an animation group, the individual animation's delegates, if set, will not be called. Also note, the removedOnCompletion property of the individual animations will be ignored when used in an animation group.

Using Keyframe animations

In this recipe, we will create a Keyframe animation to animate the position of a button over a path.

Getting ready

In Xcode, create a new Cocoa Application and name it Keyframe.

How to do it...

  1. In the project view, expand the Frameworks group and then expand Other Frameworks. Right-click on Other Frameworks and choose Add…, then choose Existing Frameworks… Find QuartzCore.framework in the list of frameworks and choose Add.
  2. Click on KeyFrameAppDelegate.h to open it.
  3. Add a variable for the NSButton in the class interface:

    NSButton *button;

  4. Next, add a property for the button variable we just added:

    @property (assign) IBOutlet NSButton *button;

  5. Finally, add the following to method declarations:

    - (void) animationButtonHit:(id)sender;
    - (CGPathRef) createPath;

  6. Click on the KeyFrameAppDelegate.m file to open it.
  7. First, we need to add an import for QuartzCore:

    #import <QuartzCore/QuartzCore.h>

  8. Add the synthesize statement for the button variable:

    @synthesize button;

  9. Add the following code to the applicationDidFinishLaunching: method:

    [[button superview] setWantsLayer:YES];

  10. Next, add the following code, which defines the animationButtonHit: method:

    - (void) animationButtonHit:(id)sender {
    CGPathRef path = [self createPath];

    CAKeyframeAnimation *animation = [CAKeyframeAnimation
    animation];
    [animation setPath:path];
    [animation setDuration:4];
    [animation setRepeatCount:1];
    [animation setRemovedOnCompletion:YES];
    [[button layer] addAnimation:animation forKey:@"position"];

    CGPathRelease(path);
    }

  11. Finally, add the following method to define the path of the animation:

    - (CGPathRef) createPath
    {
    CGFloat kMargin = 14.0;

    NSSize size = [button bounds].size;
    NSPoint position = [button frame].origin;
    NSRect frame = [[button window] frame];

    CGMutablePathRef path = CGPathCreateMutable();

    CGPathMoveToPoint(path, NULL, position.x, position.y);
    CGPathAddLineToPoint(path, NULL, kMargin, kMargin);
    CGPathAddLineToPoint(path, NULL, (frame.size.width - kMargin) -
    size.width , kMargin);
    CGPathAddLineToPoint(path, NULL, (frame.size.width - kMargin) -
    size.width, position.y);
    CGPathAddLineToPoint(path, NULL, position.x, position.y);

    return path;
    }

  12. Double-click on the MainMenu.xib file to open it in Interface Builder.
  13. Using the Size tab on the Inspector's palette, set the size of the application window to a width of 400 pixels and a height of 300 pixels.
  14. From the Library palette, drag a Push Button to the application window. Using the Size tab on the Inspector's palette, set the button's X position to 14 and its Y position to 284.
  15. Right-click on the Keyframe App Delegate in the MainWindow.xib project window to display the outlet connection window:

    Cocoa and Objective-C: Animating CALayers

  16. Drag a connection from the button outlet to the Push Button in the application window.
  17. Drag a connection from the animationButtonHit: action to the Push Button in the application window.
  18. Back in Xcode, choose Build and Run from the toolbar to run the application.

How it works...

When the application finishes launching, we call setWantsLayer:YES on the button's superview in order to enable layers on the button view.

Before we create the key frame animation, we build a CGPathRef. Our path will be built using the windows frame with an inset of fourteen pixels. The path is made up of four segments, which will represent our frames in the key frame animation.

Next, we create a key frame animation setting the path, and a duration of four seconds. We set the animation to repeat once. We also set the removedOnCompletion property to YES in order to remove the animation from the layer when it completes.

Finally, we add our animation to the buttons layer to start the animation.

There's more...

When using a path in a key frame animation, you can also set a rotation mode to specify how an object rotates as it moves over the path. Currently, there are two values that can be set for the rotation mode. The first, kCAAnimationRotateAuto, specifies that the animated object move along a tangent or just touching the path. The second value, kCAAnimationRotateAutoReverse directs the object to rotate at a 180 degree tangent to the path.

Using CAMediaTiming in animations

In this recipe, we use a timing function from the CAMediaTiming class to set the pace of an animation.

Getting ready

In Xcode, create a new Cocoa Application and name it MediaTiming.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. Click on MyView.m to open the file.
  3. Add the following awakeFromNib method:

    - (void) awakeFromNib {
    [self setWantsLayer:YES];
    }

  4. In the drawRect: method, add the following code:

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[
    self bounds] xRadius:8.0 yRadius:8.0];
    [path setClip];

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect:[self bounds]];

    [path setLineWidth:3.0];

    [[NSColor grayColor] setStroke];
    [path stroke];

  5. Click on MediaTimingAppDelegate.h to open it.
  6. Add an import for the MyView.h header file:

    #import "MyView.h"

  7. We need to add two variables to the class interface:

    NSButton *button;
    MyView *myView;

  8. Next we need to add property statements for each of the variables that we just added:

    @property (assign) IBOutlet NSButton *button;
    @property (assign) IBOutlet MyView *myView;

  9. Lastly, add a method declaration for the button's action:

    - (IBAction) animateButtonHit:(id)sender;

  10. Click on MediaTimingAppDelegate.m to open the class implementation.
  11. Add an import for QuartzCore:

    #import <QuartzCore/QuartzCore.h>

  12. In the project view, expand the Frameworks group and then expand Other Frameworks. Right-click on Other Frameworks and choose Add…, then choose Existing Frameworks… Find QuartzCore.framework in the list of frameworks and choose Add.
  13. We need to synthesize the button and myView variables:

    @synthesize button;
    @synthesize myView;

  14. Lastly, lets add the animateButtonHit: methodit$ method":

    - (IBAction) animateButtonHit:(id)sender {
    CABasicAnimation *animation = [CABasicAnimation
    animationWithKeyPath:@"opacity"];
    [animation setDuration:1.5];
    [animation setRepeatCount:1e100f];
    [animation setAutoreverses:YES];
    [animation setFromValue:[NSNumber numberWithFloat: 1.0]];
    [animation setToValue:[NSNumber numberWithFloat:0.1]];
    [animation setTimingFunction:[CAMediaTimingFunction
    functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [[myView layer] addAnimation:animation forKey:nil];
    }

  15. Double-click on the MainMenu.xib file to open it in Interface Builder.
  16. From the Library palette, drag a Custom View into the application window.
  17. Select the Identity tab from the Inspector palette and set the Class popup to MyView.
  18. Drag a Push Button from the Library palette to the application window. Set the button's title to Animate.
  19. Next, we need to set the outlets and actions. In the MainMenu.xib project window, right-click on the Media Timing App Delegate to display the outlet connections window:

    Cocoa and Objective-C: Animating CALayers

  20. Next, drag a connection from the myView outlet to the MyView Custom View.
  21. Finally, connect the animateButtonHit: action to the Push Button.
  22. In Xcode, choose Build and Run from the toolbar to run the application.

How it works...

As we have done in other recipes, we create a custom view and call setWantsLayer:YES in the awakeFromNib method to enable the use of layers in the view.

In the animateButtonHit: method, we create a CABasicAnimation using the opacity key path with a duration of a second and a half. This animation will set the opacity property of the view's layer from 1.0 to 0.1. We set the animation to repeat forever using the value of 1e100f. Setting the auto reverse property to true will cause the animation to animate while setting the opacity property of the layer back to the original values.

Finally, we set the timing function property of the animation to the kCAMediaTimingFunctionEaseInEaseOut function. Using a media timing function will set the pace of the animation.

There's more...

In this recipe, we used the kCAMediaTimingFunctionEaseInEaseOut function for our animation. Even though there are other constants that you can use for the supplied functions, the CAMediaTimingFunction class allows you to define your own timing curve using the functionWithControlPoints: method.

See also

The sample codes and projects for the recipes can be found here.

Summary

This article explained how to put CALayers into motion.


Further resources related to this subject:


About the Author :


Jeff Hawkins

Jeff Hawkins has been developing software solutions and applications for 19 years. He has worked for Adobe Systems supporting third-party developers writing plug-ins for FrameMaker on the Macintosh, Windows, and Solaris platforms. He also has worked for a startup delivering prime-time television shows via satellite to television stations across the United States. Jeff currently works in the Tools and Architecture group for ADP Inc. designing and coding solutions for enterprise payroll systems. Jeff has extensive experience working with C, C++, Objective-C, Java, and JavaScript. In his spare time, Jeff enjoys working with Apple's iOS developing mobile applications and games. Jeff is also a private pilot with a seaplane rating and has built and flown his own Van's RV-8 airplane.

Books From Packt


Core Data iOS Essentials
Core Data iOS Essentials

iPhone Applications Tune-Up: RAW
iPhone Applications Tune-Up: RAW

Cocos2d for iPhone 0.99 Beginner's Guide
Cocos2d for iPhone 0.99 Beginner's Guide

iPhone JavaScript Cookbook
iPhone JavaScript Cookbook

jQuery 1.4 Animation Techniques: Beginners Guide
jQuery 1.4 Animation Techniques: Beginners Guide

Drupal 7 Themes
Drupal 7 Themes

Blender 2.5 HOTSHOT
Blender 2.5 HOTSHOT

Python 2.6 Graphics Cookbook
Python 2.6 Graphics Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software