Skip to content

Instantly share code, notes, and snippets.

@jamesu
Created February 26, 2009 09:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jamesu/70751 to your computer and use it in GitHub Desktop.
Save jamesu/70751 to your computer and use it in GitHub Desktop.
//
// MYAnimationView.h
// http://www.cuppadev.co.uk/iphone/animated-images-on-the-iphone-sans-memory-leaks
//
// A UIView control which animates an image by simply transforming a slide image
// containing frames of the animation inside a clipped subview.
// (in other words, like a flip book except instead of flipping pages,
// you move them around in a viewport)
//
// Requirements:
// - 1 image containing the frames of your animation.
// (these can be placed sequentially on both the x and y axis)
// - 1 MYAnimationView instance with the same frame size as your animation
// (since the slide will be clipped by this view)
//
// e.g.:
//
// MYAnimationView *animView = [[MYAnimationView alloc] initWithFrame:CGRectMake(100, 100, 96, 66)];
// [window addSubview:animView];
// animView.currentAnim = @"anim"; // anim.png
// animView.isPlaying = YES; // i.e. start playing
//
// Copyright 2009 James Urquhart. All rights reserved.
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#import <UIKit/UIKit.h>
@class MYAnimationViewSlide;
@interface MYAnimationView : UIView {
MYAnimationViewSlide *slide;
NSString *currentAnim;
int currentFrame;
int numFrames;
float advanceTime;
bool isPlaying;
NSTimer *animTimer;
NSTimeInterval lastTime;
}
@property(nonatomic, readonly, retain) UIView *slide;
@property(nonatomic, copy) NSString *currentAnim;
@property(nonatomic, assign) int currentFrame;
@property(nonatomic, assign) bool isPlaying;
@end
//
// MYAnimationView.m
//
#import "MYAnimationView.h"
#import "MYAnimationViewSlide.h"
@implementation MYAnimationView
@synthesize slide;
@dynamic currentAnim;
@dynamic currentFrame;
@dynamic isPlaying;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Add the frame slide as a sub-view
slide = [[MYAnimationViewSlide alloc] initWithFrame:frame];
[self addSubview:slide];
// Reset defaults
currentFrame = 0;
numFrames = 0;
currentAnim = nil;
animTimer = nil;
lastTime = 0;
isPlaying = NO;
advanceTime = 0.0;
// Make sure we don't see all of the slide, etc
self.userInteractionEnabled = NO;
self.clipsToBounds = YES;
}
return self;
}
- (NSString*)currentAnim {
return currentAnim;
}
- (void)setCurrentAnim:(NSString*)aValue {
[currentAnim autorelease];
currentAnim = [aValue retain];
// start from fresh
NSString *path = [[NSBundle mainBundle] pathForResource:aValue ofType:@"png"];
if (path) {
// Clean transform, set image
slide.transform = CGAffineTransformMakeTranslation(0, 0);
slide.image = [UIImage imageWithContentsOfFile:path];
// Set frame length
CGSize slideSize = slide.bounds.size;
CGSize viewSize = self.bounds.size;
numFrames = floor((slideSize.width / viewSize.width) * (slideSize.height / viewSize.height));
// 25fps
advanceTime = (1.0 / 25.0);
// reset
self.currentFrame = 0;
isPlaying = NO;
}
}
- (int)currentFrame
{
return currentFrame;
}
- (void)setCurrentFrame:(int)aValue
{
currentFrame = aValue;
if (currentFrame > numFrames) {
// animation ended
[animTimer invalidate];
animTimer = nil;
isPlaying = NO;
return;
}
// Transform the slide
CGSize slideSize = slide.bounds.size;
CGSize viewSize = self.bounds.size;
// Calculate x,y position for the frame
// (assuming your frames are packed in a block rather than a strip)
int framesPerRow = floor(slideSize.width / viewSize.width);
int x = currentFrame % framesPerRow;
int y = currentFrame / framesPerRow;
slide.transform = CGAffineTransformMakeTranslation(-viewSize.width * x, -viewSize.height * y);
}
- (bool)isPlaying {
return isPlaying;
}
- (void)setIsPlaying:(bool)aValue {
if (aValue) {
// Start timer, etc
if (animTimer == nil) {
lastTime = 0;
animTimer = [NSTimer scheduledTimerWithTimeInterval:advanceTime
target:self
selector:@selector(animateTimer:)
userInfo:nil repeats:YES];
}
self.currentFrame = 0;
} else {
// Invalidate timer as we don't need it!
if (animTimer) {
[animTimer invalidate];
animTimer = nil;
}
}
isPlaying = aValue;
}
- (void)animateTimer:(NSTimer*)timer
{
NSTimeInterval timeInterval = timer.timeInterval;
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
if (lastTime != 0) {
double delta = (currentTime - lastTime) / timeInterval;
// advance by at least 1 frame, at most frames in delta
if (delta < 1)
self.currentFrame += 1;
else
self.currentFrame += (int)delta;
}
lastTime = currentTime;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
}
- (void)dealloc {
[slide release];
[currentAnim release];
if (animTimer)
[animTimer invalidate];
[super dealloc];
}
@end
//
// MYAnimationViewSlide.h
//
#import <UIKit/UIKit.h>
@interface MYAnimationViewSlide : UIView {
UIImage *image;
}
@property(nonatomic, retain) UIImage *image;
@end
//
// MYAnimationViewSlide.m
//
#import "MYAnimationViewSlide.h"
@implementation MYAnimationViewSlide
@dynamic image;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}
- (UIImage*)image {
return image;
}
- (void)setImage:(UIImage*)aValue {
[image release];
image = [aValue retain];
// Resize to fit
CGSize sz = image.size;
self.frame = CGRectMake(0, 0, sz.width, sz.height);
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
// Just draw the image!
[image drawAtPoint:CGPointMake(0, 0)];
// No more need for the image
[image release];
image = nil;
}
- (void)dealloc {
[image release];
[super dealloc];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment