Created
February 26, 2009 09:29
-
-
Save jamesu/70751 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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