Advertise Mobile SDKs Books Events Forum News Social Networking Support Us
Follow @iphonedevsdk on Twitter

Interface 2, Advanced iOS
Mockup & Code Gen
($9.99)

Make your own iPhone apps
and run them live!
(free)

Pic Frame Dynamo: Photo Editing
($0.99)

Abiliator
($1.99)

Want your application or service advertised on iPhone Dev SDK?

Go Back   iPhone Dev SDK Forum > iPhone SDK Development Forums > iPhone SDK Tutorials

Reply
 
LinkBack Thread Tools Display Modes
Old 06-07-2011, 01:24 PM   #1 (permalink)
Cocoa Junkie
 
Duncan C's Avatar
 
Join Date: Dec 2008
Location: Northern Virginia
Posts: 6,005
Duncan C has a spectacular aura about
Default Using animation groups to create a linked sequence of animation steps

Lately I've been reading up on Core Animation (CA), and learning how to use a wider range of it's features.

Our company develops for both Mac and iOS, so where practical, I like to use techniques that are the same across platforms.

The Mac OS flavor of CA doesn't support view animation, or the new block-based animations that are available in iOS. On the other hand, Mac OS objects like views and windows have an animator proxy object, and you can simply send messages to change properties to the proxy instead of sending them to the view, and the proxy creates an animation for you.

Both platforms support CAAnimation objects including CABasicAnimation and CAAnimationGroup.

On iOS, I've written cartoon style animations by using a mixture of view animation, CABasicAnimation, and CAKeyframeAnimation objects. That involves setting up completion callbacks that increment a counter and trigger the next step in the animation. There's a link to a tutorial in my sig that uses that approach, taken our of my company's "Kevin and Kell" cartoon reader.

I wanted to see if you could use animation groups to link a sequence of animations. It turns out you can, although there are some gotchas.

I created a project first in Mac OS, and then ported it over to iOS. The core of the code is the same between the two projects. the differences are that Mac OS uses NSView, and iOS uses UIViews, CGRects, CGPoints, etc. That was about all I needed to change to move the code from Mac to iOS.

The animation itself is fairly useless. It just animates our company logo, fading in, moving diagonally across the screen, rotating, falling (with an slide whistle sound), rotating back to it's original orientation, sliding sideways back to it's starting position, and then fading out again.

You can download the project at this link:

Creating a linked set of animations using a CAAnimationGroup

The core of the program is a routine called -doAnimation that triggers the animation sequence.

It creates a whole bunch of CABasicAnimation objects, each with a start time that is after the end of the previous animation. It then creates a CAAnimationGroup object and installs all the individual animations into that group. Its sets the duration of the animation group to the total time for all the component animations.

I created a variable that keeps track of the total time for all the animations so far, so it can figure out when to start the next animation in the sequence.

The animations are installed on the layer that backs the image view I am animating.

In order for each animation to build on the previous animation, you have to set fillMode = kCAFillModeForwards and removedOnCompletion = FALSE (which leaves the animation's effects in force once it's finished.) You have to do those steps for every animation in the group AND for the group animation. I tried setting removedOnCompletion to TRUE for one of the animations in the group, and it behaves very strangely.

I wanted to add a sound to the mix at a specific point in the animation sequence. Normally, with CABasicAnimation, you can set an object up as a delegate of the animation, and if it finds a animationDidStop:finished: method in the delegate, it will call it once that animation is complete. Apparently that doesn't work when an animation is part of a group. I think the reason is that the animationDidStop method is called "when the animation completes its active duration or is removed from the object it is attached to." In this case, the animation is part of a group, so it doesn't get removed from the group it belongs to.


The docs say that animations don't actually change the underlying properties that they animate (location, frame, opacity, transform, etc.) but only create the appearance of that property being changed. They instruct you to change the underlying value through code after submitting the animation if you want the layer to keep the changes applied by the animation. However, there's a gotcha. CAAnimations are added to layers, not view objects. By default, layers do an implicit animation when you change one of their values. Simply executing the statement

Code:
layer.opacity = 0.0;
Causes the layer to fade away. You have to use a block of code like this if you want a change to a layer setting not to animate:

Code:
  [CATransaction begin];
  [CATransaction setValue: [NSNumber numberWithBool: YES]
    forKey: kCATransactionDisableActions];
  imageOne.layer.opacity = 0.0;
  [CATransaction commit];
Anyway, I will post all the code to the -doAnimation method in a reply to this thread (the forum won't let me post it here because the post would be too long. Sigh...)

One of the Core Animation books I'm reading says that you can actually use a CAAnimation object to animate properties in other objects than just the target for the animation. The only requirement is that you be able to build a key KVO expression that links to the target object (e.g. "parentView.sprite1View.legView.layer.position").

I had visions of targeting a group animation at a parent view, and then reaching into the subviews of the parent to animate crossfades, animate multiple views independently, etc. However, you can't add "run this animation now" animations to views. Only layers support that type of animation, and layers don't lend themselves to linking with KVO expressions like views do. In order to do the parentView.sprite1View.legView.layer.position" example I gave above, I would have to make most/all the views in the view hierarchy custom subclasses of UIImageView (or NSImageView on the Mac) and include properties that link to the other views. As far as I know, you can't create subclasses of layers, since layers are created by the system automatically for the views they are linked to.
__________________
Regards,

Duncan C
WareTo

Check out our apps in the Apple App store


Check out this password generator app that shows various techniques including using a data container singleton object to share data between objects in your project.

See this tutorial on using UIView animations and layer animations:

See this thread on generating random, non-repeating text

Check out a very cool Macintosh Kaleidoscopes app called ScopeWorks that we released to the Mac App store.
Duncan C is offline   Reply With Quote
Old 06-07-2011, 01:25 PM   #2 (permalink)
Cocoa Junkie
 
Duncan C's Avatar
 
Join Date: Dec 2008
Location: Northern Virginia
Posts: 6,005
Duncan C has a spectacular aura about
Default The code for the -doAnimation method from this project

Here is the code for the workhorse -doAnimation method from this project:


Code:
- (IBAction) doAnimation: (id) sender;
{
  self.animationInFlight = TRUE;
  CGPoint oldOrigin;
  CGFloat duration = 0.4;
  CGFloat totalDuration = 0.0;
  CGFloat pause = 0.01;
  CGFloat start = 0;
  
  //First animate the opacity to 1.0
  CABasicAnimation* show =  [CABasicAnimation animationWithKeyPath: @"opacity"];
  show.removedOnCompletion = FALSE;
  show.fillMode = kCAFillModeForwards;
  show.duration = duration;
  show.beginTime = start;
  
  start = start + duration + .1;  //Calculate the start of the next animation.
  show.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
  [show setToValue: [NSNumber numberWithFloat: 1.0]];
    
  //Move the image up and to the right
  oldOrigin = imageOne.layer.position;
  CGPoint newOrigin = CGPointMake(oldOrigin.x + 150, oldOrigin.y - 250);
  CABasicAnimation* move =  [CABasicAnimation animationWithKeyPath: @"position"];
  move.removedOnCompletion = FALSE;
  move.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  move.fillMode = kCAFillModeForwards;
  move.duration = duration;
  move.beginTime = start;
  start = start + duration + pause;
  move.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  [move setToValue: [NSValue valueWithCGPoint: newOrigin]];
  
  //Rotate it 90 degrees
  CABasicAnimation* rotate =  [CABasicAnimation animationWithKeyPath: @"transform.rotation.z"];
  rotate.removedOnCompletion = FALSE;
  rotate.fillMode = kCAFillModeForwards;
  rotate.duration = duration;
  rotate.beginTime = start;
  //Setting an animation's delegate should cause the animation to call animationDidStop:finished
  //when it completes, but it doesn't seem to work when an animation is part of a group
  //rotate.delegate = self; //This doesn't work!
  start = start + duration + pause;
  [self performSelector: @selector(playDropSound) withObject: nil afterDelay: (NSTimeInterval)start];
  rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  [rotate setToValue: [NSNumber numberWithFloat: -M_PI / 2]];
  
  //Move it down
  newOrigin = CGPointMake(newOrigin.x, newOrigin.y + 250);
  CABasicAnimation* moveDown =  [CABasicAnimation animationWithKeyPath: @"position"];
  moveDown.removedOnCompletion = FALSE;
  moveDown.fillMode = kCAFillModeForwards;
  moveDown.duration = 0.8;
  moveDown.beginTime = start;
  moveDown.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
  start = start + moveDown.duration + pause;
  [moveDown setToValue: [NSValue valueWithCGPoint: newOrigin]];
  
  //Rotate it back
  CABasicAnimation* rotateBack =  [CABasicAnimation animationWithKeyPath: @"transform.rotation.z"];
  rotateBack.removedOnCompletion = FALSE;
  rotateBack.fillMode = kCAFillModeForwards;
  rotateBack.duration = duration;
  rotateBack.beginTime = start;
  start = start + duration + pause;
  rotateBack.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  [rotateBack setToValue: [NSNumber numberWithFloat: 0.0]];
  
  //Shift it back to the left
  CABasicAnimation* moveBack =  [CABasicAnimation animationWithKeyPath: @"position"];
  moveBack.removedOnCompletion = FALSE;
  moveBack.fillMode = kCAFillModeForwards;
  moveBack.duration = duration;
  moveBack.beginTime = start;
  moveBack.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  start = start + moveBack.duration + pause;
  [moveBack setToValue: [NSValue valueWithCGPoint: oldOrigin]];

  //Finally, hide it again.
  start +=.2; //Wait a little longer before hiding the image.
  CABasicAnimation* hide =  [CABasicAnimation animationWithKeyPath: @"opacity"];
  hide.removedOnCompletion = FALSE;
  hide.fillMode = kCAFillModeForwards;
  hide.duration = duration;
  hide.beginTime = start;
  totalDuration = start + duration + pause; //Calc the total duration of all animations
  rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  [hide setToValue: [NSNumber numberWithFloat: 0.0]];
  
  //Put all the animations into a group.
  CAAnimationGroup* group = [CAAnimationGroup animation];
  [group setDuration: totalDuration];  //Set the duration of the group to the time for all animations
  group.removedOnCompletion = FALSE;
  group.fillMode = kCAFillModeForwards;
  [group setAnimations: [NSArray arrayWithObjects: show, move, rotate, moveDown, rotateBack, moveBack, hide, nil]];
  [imageOne.layer addAnimation: group forKey:  nil];
  //-----
  
  //Queue up a timer to do cleanup once the group animation is finished.
  [NSTimer scheduledTimerWithTimeInterval: totalDuration 
                                   target: self 
                                 selector: @selector(animationCleanup)
                                 userInfo: nil 
                                  repeats: NO];
}
__________________
Regards,

Duncan C
WareTo

Check out our apps in the Apple App store


Check out this password generator app that shows various techniques including using a data container singleton object to share data between objects in your project.

See this tutorial on using UIView animations and layer animations:

See this thread on generating random, non-repeating text

Check out a very cool Macintosh Kaleidoscopes app called ScopeWorks that we released to the Mac App store.
Duncan C is offline   Reply With Quote
Old 12-14-2011, 02:12 PM   #3 (permalink)
Registered Member
 
Join Date: Feb 2009
Posts: 218
malaki1974 is on a distinguished road
Default

Exactly what I was looking for. Thank you.
malaki1974 is offline   Reply With Quote
Reply

Bookmarks

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is On
Trackbacks are On
Pingbacks are On
Refbacks are On



» Advertisements
» Online Users: 478
14 members and 464 guests
alexeir, David-T, Dj_kades, foslock, iAppDeveloper, jeroenkeij, LunarMoon, Mijator, Pauluz85, pipposanta, QuantumDoja, robsmy, sacha1996, usernametaken
Most users ever online was 1,387, 04-10-2012 at 04:21 AM.
» Stats
Members: 175,679
Threads: 94,129
Posts: 402,928
Top Poster: BrianSlick (7,990)
Welcome to our newest member, xzoonxoom
Powered by vBadvanced CMPS v3.1.0

All times are GMT -5. The time now is 09:24 AM.
Powered by vBulletin® Version 3.8.0
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.0