On one of my first projects, I needed to do simple sprite animations using UIImageView. When I got into it, I quickly realized that I didn’t have the level of control that I needed. So I created LIImageView and LIAnimationManagerView. I added the ability to load tile based sprite sheets, numbered sequenced images, UIImage decompression on loading and most importantly Observer Notifications on animation events (Start, Stop, Pause and Loop).
I have attached an example Xcode Project that shows most of all of the functionality of these classes, but play around with it and you’ll soon see the benefit over using the standard animations of UIImageView.
As you can see below, steps 1-6 are pieces of code from the viewDidLoad selector from the projects main ViewController.
Step 1: Importing Headers into View Controller
First things first, let’s import the header files for the new image view and animation manager.
#import "LIImageView.h"
#import "LIAnimationManagerView.h"
Step 2: Load Sprite Sheet
Next, we are going to load the animation sprite sheet. This sprite sheet has all the image animation frames for all the different states of the animation sequence. They are tiled into columns and rows and spaced with a specific Rect size. First we created a string with path to the file resource. Then we load the file into a UIImage using the decompression category class extension I added. This category extension decompresses the image into memory, during the initialization of the UIImage object, so that you don’t see a lag when the animation plays for the first time. Lastly, we define a Rect variable to use with this sprite sheet.
// Load Image
NSString *fileLocation = [[NSBundle mainBundle] pathForResource:@"BrianSheet.png" ofType:nil];
UIImage *image = [[UIImage alloc] initWithContentsOfFileDecompressed:fileLocation];
CGRect spriteRect = CGRectMake(0, 0, 61, 85);
Step 3: Build Animations From Sprite Sheet
Now we are going to build the LIImageView objects. We initialize the object with a tile sprite sheet and pass a start index, end index and a rect size of the sprite. If the sprite sheet dimensions are not dividable by the Rect dimensions then a nil is returned. Indexes are zero based and start in the upper left of the sprite sheet and wrap when it reaches the end of a row. If we have a 5×5 sprite sheet, the first row would contain indexes 0-4, second row would contain 5-9 and so on.
Next we set the repeat count of the animation loops. This is the same as UIImageView, the default value is 0, which specifies to repeat the animation indefinitely.
Now we can set the frame duration of the animation. I preferred this way setting animation timing rather than setting an animation duration. I have included several predefined values for 5, 10, 15 and 30 FPS, but all you have to do is provide a double value.
// Build Drink Animations
LIImageView *drinkIn = [[LIImageView alloc] initWithTileImage:image fromIndex:12 toIndex:15 fromRect:spriteRect];
drinkIn.animationRepeatCount = 1;
drinkIn.animationFrameDuration = ImageAnimator10FPS;
LIImageView *drinkIdle = [[LIImageView alloc] initWithTileImage:image fromIndex:15 toIndex:15 fromRect:spriteRect];
drinkIdle.animationRepeatCount = 10;
drinkIdle.animationFrameDuration = ImageAnimator5FPS;
LIImageView *drinkOut = [[LIImageView alloc] initWithTileImage:image fromIndex:16 toIndex:22 fromRect:spriteRect];
drinkOut.animationRepeatCount = 1;
drinkOut.animationFrameDuration = ImageAnimator10FPS;
Step 4: Create Animation Manager and Add Animations
Here, we are going to set up an animation manager. This manager is a UIView and is responsible for storing animations by a state name and the loading and unloading playback controls of these animations by state. The managers Rect is usually the same size as the sprite animation size and can be placed anywhere in it parents view.
Animations are added to the manager with a string defining its state. I guess I should have called it a Key, but oh well.
// Create Animation Manager
animationManager = [[LIAnimationManagerView alloc] init];
animationManager.frame = CGRectMake(129, 85, 61, 85);
[self.view addSubview:animationManager];
// Add Drink Animations to Manager
[animationManager addAnimation:drinkIn withState:@"drinkIn"];
[animationManager addAnimation:drinkIdle withState:@"drinkIdle"];
[animationManager addAnimation:drinkOut withState:@"drinkOut"];
Step 5: Add Observers to LIImageView Objects
Here is the part that I really like. Each LIImageView will dispatch a notification when the state of the animation changes (Start, Stop, Pause and Loop). This way I can define a controlled sequence of animations. “drinkIn” loops once and the triggers “drinkIdle” when done. “drinkIdle” plays 10 times then triggers “drinkOut” when it finishes. There are countless possibilities and allows a lot of customization.
So, lets define what we want to happen when a state changes. The code below defines that when the drinkIn animation stops, start playing the drinkIdle animation. The callback selector playDrinkIdleAnimation tells the animation manager to play animation by state “drinkIdle”.
// Drink Event Listeners
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playDrinkIdleAnimation)
name:ImageAnimatorDidStopNotification
object:drinkIn];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playDrinkOutAnimation)
name:ImageAnimatorDidStopNotification
object:drinkIdle];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playWalkAnimation)
name:ImageAnimatorDidStopNotification
object:drinkOut];
Step 6: Clean Up and Release
Now that we have built all the animations from the sprite sheet and added all animations to the manager, we can release our reference to them. It’s now the managers responsibility to release.
// Clean Up
[image release];
[drinkIn release];
[drinkIdle release];
[drinkOut release];
Step 7: Create Notification Callback Selectors
Lastly, we add a few notification callback selectors to tell the animation manager to play an animation by state.
// Drink
- (void) playDrinkInAnimation {
[animationManager playAnimationByState:@"drinkIn"];
}
- (void) playDrinkIdleAnimation {
[animationManager playAnimationByState:@"drinkIdle"];
}
- (void) playDrinkOutAnimation {
[animationManager playAnimationByState:@"drinkOut"];
}
// Walk
- (void) playWalkAnimation {
[animationManager playAnimationByState:@"walk"];
}
That’s about it. Please download the LIImageViewSample Xcode project and play around with it. Please feel free to let me know if you find any bugs, defects or have any suggestions.
Source Xcode Project: LIImageViewSample.zip