// // 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 @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 @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