I am working on an iPhone app which is related to stock market.
There is a requirement to create a horizontal scroller which is similar to a stock ticker.
What should I do to achieve that?
This sort of thing is a damn nuisance and (as far as I know) there is no easy existing way.
First you must simplify K.I.S.S. by making each block the same length. Here's how to do it..
(1) make a standard width (say, 100 px) for each "block".
The blocks would simply be UIViews (or perhaps just text labels). Are you comfortable with creating UIViews on the fly? If not we will tell you how to do it!
(2) pick a start point for the journey which is just to the right completely offscreen
(3) pick an end point for the journey which is just to the left completely offscreen
(3b) choose the exact time (say "4.71937s") you want for the travel time from point1 to point2
(4) figure out precisely (as in exactly precisely!) the seconds it takes for the block to travel it's own length from right to left. let's say 0.91763 seconds
You have to use NSTimers to do this. Are you familiar with them?
(5) set up a recurring 0.91763s timer which: makes a new block and
(5b) sucks off the next, if any, piece of text information (say "AAPL 408.50") and
(5c) places it at the start point2 and
(5d) simply using core animation begins it animating towards the end point3 and
(5e) launch an individual one shot timer for this block, which will destroy this block after the overall time mentioned in 3b
That's it.
You will have to set up a simple FIFO stack for the text items (whatever you you choose), as you get them from whatever source, shove them in to it.
Are you comfortable setting up some sort of array to use as a stack of info? Again if not we can help! :)
Note that in (5b) you would likely shove the one you just used back on the other end of the stack so they all keep going forever. It's quite likely you would have to be able to touch individual items to delete them or, eg, modify the price or whatever as new info comes in.
Once you get this working ("standard block length method"). You may prefer to somehow work out the exact length of each text block (AAPL 400.50 is longer than AAPL 30)...to do so...
Calculate on the fly a new own-length time value (as in point 4) for each block just for that block. ie, do that at point 5.b.2. instead of using a recurring timer (in point 5), fire off a new timer (at 5f) to launch the next block. (To be clear, points 3b and 5e are unchanged.)
Hope it helps!
If I understand the question aright, then one simple solution is just to update a UILabel using an NSTimer (see quickly knocked up code below). For some applications this might be enough, but if you need to keep updating the string with fresh data then you'll need to do some more work, e.g. so the new string is appending 'off screen' as it were.
This simple solution does not give you particularly smooth scrolling. Rather it jumps one character width at a time and, with the default system font, not every character is as wider as every other.
To do smoother scrolling, you could render the string into a graphics context (use drawInRect:withFont: on an NSString), make a UIImage, and then nudge that along by n pixels at a time. When you get near to the right margin of the image, you'll need to render the image a second time to the right of the end of the first copy.
Here's the simple code (with .h and .m files combined) that demonstrates the crude approach:
// stock ticker example code
//
// Created by Matthew Elton on 27/12/2010.
// http://www.obliquely.org.uk/blog/app All rights reserved.
//
#define TICKER_WIDTH 250.0
#define TICKER_FONT_SIZE 18.0
#define TICKER_RATE 0.2 // nudge along five times a second
#import <UIKit/UIKit.h>
#interface tickerAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
NSString* tickerString;
UILabel *tickerLabel;
}
#property (nonatomic, retain) NSString* tickerString;
#property (nonatomic, retain) UILabel* tickerLabel;
#property (nonatomic, retain) IBOutlet UIWindow *window;
- (void) nudgeTicker: theTimer;
#end
#implementation tickerAppDelegate
#synthesize window, tickerString, tickerLabel;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setTickerString: #"This is the sample string to use on the ticker. It is quite long. So I need to fill it out with random content. "];
CGSize labelContentSize = [[self tickerString] sizeWithFont: [UIFont systemFontOfSize:TICKER_FONT_SIZE] forWidth: 2000.0 lineBreakMode: UILineBreakModeClip];
[self setTickerLabel: [ [ [UILabel alloc] initWithFrame:CGRectMake(20,40,TICKER_WIDTH, labelContentSize.height)] autorelease]];
[[self tickerLabel] setFont: [UIFont systemFontOfSize:TICKER_FONT_SIZE]];
[[self tickerLabel] setText: [self tickerString]];
[[self tickerLabel] setBackgroundColor:[UIColor lightGrayColor]];
[[self tickerLabel] setLineBreakMode:UILineBreakModeClip];
[NSTimer scheduledTimerWithTimeInterval:TICKER_RATE target:self selector: #selector(nudgeTicker:) userInfo:nil repeats:YES];
[window addSubview:[self tickerLabel]];
[self.window makeKeyAndVisible];
return YES;
}
- (void) nudgeTicker: theTimer;
{
NSString* firstLetter = [[self tickerString] substringWithRange: NSMakeRange(0,1)];
NSString* remainder = [[self tickerString] substringWithRange:NSMakeRange(1,[[self tickerString] length]-1)];
[self setTickerString: [remainder stringByAppendingString: firstLetter]];
[[self tickerLabel] setText:[self tickerString]];
}
- (void)dealloc {
[window release];
[tickerString release];
[tickerLabel release];
[super dealloc];
}
#end
Related
EDIT: I apologize for wasting time, the erorr had nothing to do with what I'm taking about but rather some logic in my code that made me believe this was the cause. I'm awarding Kevin with the correct answer since using his idea to pass the whole AuthorSelectionView, and his note on correcting the NSNumer mistake. Sorry about that.
I've been trying to figure this out for hours, and even left it alone for a day, and still can not figure it out...
My situation is as follows:
I've created a custom class that implements 'UIView' and made this class into a protocol as follows:
custom UIView h file
#protocol AuthorSelectionViewDelegate <NSObject>
-(void)AuthorSelected:(NSNumber *)sender;
#end
#import <UIKit/UIKit.h>
#interface AuthorSelectionView : UIView
#property (nonatomic,assign) id<AuthorSelectionViewDelegate> delegate;
#property (strong,retain) NSNumber *authorID;
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID ;
#end
the implementation...
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID
{
self = [super initWithFrame:frame];
if (self) {
self.authorID = [[NSNumber alloc] initWithInt:authorID]; //used to distinguish multiple instances of this class in a view.
...
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)];
[button addTarget:self action:#selector(CUSTOMBUTTONCLICK) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) CUSTOMBUTTONCLICK
{
[self.delegate performSelector:#selector(AuthorSelected:) withObject:self.authorID];
}
Now the method in my delegate object gets called just fine, but my major problem here is that something is going on with the object being pass through when i have multiple instances of the AuthorSelected class alloc'd.. (the NSNumber authorID). I'm getting some weird behavior with it. It seems almost random with the value being passed, but i'm detecting some pattern where the value passed through is coming up late..
thats confusing so ill try to explain:
I create two instances of the AuthorSelected view, one with authorID=1 and the other with authorID=2.
On the first press, lets say i press the first button, i'll get 1 as expected.
On the second press, if I press the 1st custom button, i'll get '1', but if i press the second i'll still get 1.
On the third go, either button will give me back '2'
I feel like this is some issue with pointers since that has always been a weak point for me, but any help would be greatly appreciated as I can not seem to figure this one out.
Thank you!
EDIT:
as requested here is how I create the AuthorSelectionView Objects...
AuthorSelectionView * asView01 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic1
withLabel:randomUserName
withID:1];
asView01.delegate = self;
AuthorSelectionView * asView02 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic2
withLabel:randomUserName2
withID:2];
asView02.delegate = self;
A detail that may be important:
As soon as i click on one of these custom views, my code is set to (for now) call the method that runs the above AuthorSelectionView alloc code, so that i can refresh the screen with the same layout, but with different userpic/userName. This is poor design, I know, but for now I just want the basic features to work, and will then worry about redrawing. I metion this tidbit, becuase I understand that objective-c 'layers' veiws on top of eachother like paint on a canvas, and had a thought that maybe when I click what I think may be my 2nd button, its really 'clicking' the layer beneath and pulling incorrect info.
Your description of the problem is a bit confusing, but this line in your init is very clearly wrong:
self.authorID = [self.authorID initWithInt:authorID];
In -init, your property self.authorID defaults to nil, so the expression [self.authorID initWithInt:authorID] is equivalent to [nil initWithInt:authorID], which evaluates back to nil. So you should actually be seeing nil in your action. You probably meant to say self.authorID = [NSNumber numberWithInt:authorID]
You're missing the alloc message, so this message:
self.authorID = [self.authorID initWithInt:authorID];
Is sent to a nil target, because self.authorID hasn't been allocated yet.
So first allocate it, then use the init method, or mix these two messages. A faster syntax allows to do it this way:
self.authorID= #(authorID);
EDIT
I don't see where you initialize the delegate, that method shouldn't even be called if you haven't initialized it. Show the code where you create the AuthorSelectionView objects and set the delegates.
instead of :
self.authorID = [self.authorID initWithInt:authorID];
put :
self.authorID = [NSNumber numberWithInt:authorID];
or
self.authorID = [[NSNumber alloc] initWithInt:authorID];
EDIT :
Don't you have errors or warnings in your code ? I can't see you returning self object in the init method ("return self;")
I'm trying to developed a really simple browser to be part of an iPad app. That browser will have tabs. All tabs are based on a .xib with an UIWebView filling almost all .xib frame.
I store all them inside an NSMutableArray, called tabsArray.
So, here's how I add a new tab to the array:
declaration:
.h
#property (nonatomic,strong) NSMutableArray *tabsArray;
#property (nonatomic,strong) UIDetailWebController *pagina;
.m
self.pagina = [[UIDetailWebController alloc] initWithNibName:#"UIDetailWebController" bundle:nil];
[tabsArray insertObject:pagina atIndex:currentViewTag+1];
how I display it on screen:
[self.view addSubview:[[tabsArray objectAtIndex:currentViewTag+1] view]];
And, finally, here's how I'm trying to "release" the UIWebView when user closes a tab (with a specific index):
[[[tabsArray objectAtIndex:index] view] removeFromSuperview];
[[tabsArray objectAtIndex:index] setWebView:nil];
[[tabsArray objectAtIndex:index] setView:nil];
[tabsArray removeObjectAtIndex:index];
My problem is: It appears that by doing this I simply don't release it. Memory consumption keeps the same, and If I'm playing an youtube video, the audio continues to play.
I'm kind of new on programming, and started iOS development by iOS 5, with ARC, so probably I'm letting slide some basic detail related to memory management.
SOLUTION:
Ok, I found out that what was retaining webview was the implementation of PullToRefreshView (https://github.com/chpwn/PullToRefreshView). When I set it's delegate to nil; everything just works!
The property pagina is is a strong property, so it retains the value that you assign to it in:
self.pagina = [[UIDetailWebController alloc] initWithNibName:#"UIDetailWebController" bundle:nil];
So you should set it to nil:
[tabsArray removeObjectAtIndex:index];
self.pagina = nil; // Add this line.
Finally, not that the following lines may not be needed:
[[tabsArray objectAtIndex:index] setWebView:nil];
[[tabsArray objectAtIndex:index] setView:nil];
And that the line:
[[[tabsArray objectAtIndex:index] view] removeFromSuperview];
may be simplified into:
[self.pagina removeFromSuperview];
I think the problem is that you store all your UIDetailWebControllers in an array. The array is keeping a strong pointer to your DetailWebController.
You should use:
[tabsArray removeObjectAtIndex:index];
And then set
self.pagina = nil;
After calling [self.pagina removeFromSuperView]; it should work.
Happy November to all,
Well I tried Xcode Build and analyze on my project, and it showed some unusual leaks, which I couldn't quite accept with my knowledge of Objective C.
So I decided to put up a test project and ask here..
MemoryTestController.h
#interface MemoryTestController : UIViewController{
UIImageView *tstImageView;
}
#property(nonatomic,retain) UIImageView *tstImageView;
#end
MemoryTestController.m
#implementation MemoryTestController
#synthesize tstImageView;
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release];
}
-(void)dealloc{
[tstImageView release];
[super dealloc];
}
#end
When I try Build and analyze, clang static analyzer say
Potential leak of an object at line xx
And the culprit line is
self.tstImageView = [[UIImageView alloc]initWithFrame:<SomeFrame>];
I think I am releasing once for every time I am allocing/retaining. Am I missing something, or Static analyzer has some bugs?
EDIT : Is there any leak there?
Well I run the above project using Leak tool in instrument..It didn't show any leak even though I tried many times..Whom should I believe? Static analyzer or Leak instrument?
your problem is how you release it:
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release]; // << here
}
you should do it this way:
- (void)viewDidLoad{
[super viewDidLoad];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
imageView.image = [UIImage imageNamed:#"SomeImage.png"];
self.tstImageView = imageView;
[imageView release];
[self.view addSubview:self.tstImageView];
}
The checker is correct because it cannot assume that the variable is identical to the one you set. Therefore, the form you use in the OP could introduce a reference count imbalance because the ivar's value may not be what you assigned to it by the time you message release upon the ivar.
These cases are not likely for a UIImageView, and quite unlikely in the context of your program, but these examples should give you an idea as to why the checker assumes that object->ivar associations shall not be trusted:
Between creation of the image view and the message to release it via the ivar, you have:
self.tstImageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
1) assignment of the image view via the setter
2) access of the image view via the getter
3) direct access of the ivar, when adding to self.view
the setter may have taken a copied or used a cached value. UIImageView is a bad example, but the checker does not know how types are generally passed around - even if it did, it would (at times) make unsafe assumptions.
the simplest example would be:
- (void)setName:(NSString *)inName {
NSString * prev = name;
if (inName == prev) return;
if (0 == [inName count]) name = #"";
else name = [inName copy];
[prev release];
}
the value held by the ivar could change in the meantime. not likely an issue in this case, but let's say that adding the image view as the subview could end up calling back and altering self in the process/effect of adding the subview, and replacing or removing the image view you passed. In that case, the variable view you passed would leak and the view it replaced it with would have a negative imbalance.
Neither of those are likely to happen in your example, but it does happen in real world programs, and the checker is correctly evaluating based on locality, not property (the checker can't assume much of what happens inside a method call). It also encourages one good idiomatic style in this case.
EDIT : Is there any leak there?
Well I run the above project using
Leak tool in instrument..It didn't shown any leak even though I tried
it many times..Whom should I believe? Static analyzer or Leak
instrument?
The static analyzer says there is a potential leak because it is unable to guarantee the reference/allocation it follows is correctly retained/released. You can guarantee that reference counting is correct and please the static analyzer by changing you program to look like I wrote it in my example.
The way you have written it has made it impossible for the analyzer to follow the reference.
If you have no leaks and no zombies, then there is not a leak. But the solution is easy to fix - and programs have a way of changing during development. It's much easier to use the form I posted so it is easier for the toolset and for you to verify the program is correct. The static analyzer is not always correct, but you should adjust your programs to please it because static analysis is very useful. The program I posted is also easier for a human to understand and confirm that it is correct.
when you declare a property with retain like this
#property(nonatomic,retain) UIImageView *tstImageView;
a setter is added that will incr the retainCount when you assign to the property. When you do as below the object you created has already a retainCount == 1
self.tstImageView = [[UIImageView alloc]
initWithFrame:<SomeFrame>];
so the tstImageView object has 2 in retainCount.
do instead
UIImageView* view = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView = view;
[view release];
then, although unrelated to your leak when you release it write like this instead
self.tstImageView = nil;
since the setter will then will properly set the retainCount
I'm using TTLauncherView as a sort of home screen for my app and I only have one page's worth of icons. How can I make it so the TTLauncherView won't let you drag icons to "the next page"? I want to set a maximum number of pages (in this case one.)
(EDIT: long story short, I subclassed beginEditing, see the answer below.)
I see where why it adds an extra page when beginEditing is called, but I don't want to edit the framework code. (That makes it hard to update to newer versions.) I'd also prefer not to subclass and override that one method, if I have to rely on how it's implemented. (I'm not against subclassing or adding a category if it's clean.)
I tried setting scrollView.scrollEnabled to NO in the callback method launcherViewDidBeginEditing in my TTLauncherViewDelegate, but that doesn't work while it's in editing mode and I don't know why.
I tried adding a blocker UIView to the scrollview to intercept the touch events by setting userInteractionEnabled=NO, which works OK. I still have to disable the dragging of TTLauncherItems to the next page somehow.
I also tried setting the contentSize of the scrollview to it's bounds in launcherViewDidBeginEditing, but that didn't seem to work either.
Is there a better way?
Tried to block gestures:
- (void)setLauncherViewScrollEnabled:(BOOL)scrollEnabled {
if (scrollEnabled) {
[self.scrollViewTouchInterceptor removeFromSuperview];
self.scrollViewTouchInterceptor = nil;
} else {
// iter through the kids to get the scrollview, put a gesturerecognizer view in front of it
UIScrollView *scrollView = [launcherView scrollViewSubview];
self.scrollViewTouchInterceptor = [UIView viewWithFrame:scrollView.bounds]; // property retains it
UIView *blocker = self.scrollViewTouchInterceptor;
[scrollView addSubview:scrollViewTouchInterceptor];
[scrollView sendSubviewToBack:scrollViewTouchInterceptor];
scrollViewTouchInterceptor.userInteractionEnabled = NO;
}
}
For reference: TTLauncherView.m:
- (void)beginEditing {
_editing = YES;
_scrollView.delaysContentTouches = YES;
UIView* prompt = [self viewWithTag:kPromptTag];
[prompt removeFromSuperview];
for (NSArray* buttonPage in _buttons) {
for (TTLauncherButton* button in buttonPage) {
button.editing = YES;
[button.closeButton addTarget:self action:#selector(closeButtonTouchedUpInside:)
forControlEvents:UIControlEventTouchUpInside];
}
}
// Add a page at the end
[_pages addObject:[NSMutableArray array]];
[_buttons addObject:[NSMutableArray array]];
[self updateContentSize:_pages.count];
[self wobble];
if ([_delegate respondsToSelector:#selector(launcherViewDidBeginEditing:)]) {
[_delegate launcherViewDidBeginEditing:self];
}
}
I think overriding beginEditing in TTLauncherView is your best bet. Since you'd only really be touching one method (and only a few lines in that method), upgrading it when the time comes shouldn't be too bad. Since that method explicitly adds the extra page, I'm not sure how you'd get around it w/o editing that specific piece of code
As Andrew Flynn suggested in his answer, I was able to make it work by subclassing and overriding the beginEditing method to remove the extra page TTLauncherView adds when it goes into editing mode.
One problem I'm having is I can't figure out how to remove the warning I get for calling the (private) method updateContentSize on my subclass. I tried casting it to id, but that didn't remove the warning. Is it possible?
edit: I was able to remove the warning by using performSelector to send the message to the private class. (I had previously create a category method performSelector:withInt that wraps NSInvocation so that I can pass primitives via performSelector methods, which makes this very convenient.)
// MyLauncherView.h
#interface MyLauncherView : TTLauncherView {
NSInteger _maxPages;
}
#property (nonatomic) NSInteger maxPages;
#end
// MyLauncherView.m
#implementation MyLauncherView
#synthesize maxPages = _maxPages;
- (void)beginEditing {
[super beginEditing];
// ignore unset or negative number of pages
if (self.maxPages <= 0) {
return;
}
// if we've got the max number of pages, remove the extra "new page" that is added in [super beginEditing]
if ([_pages count] >= self.maxPages ) {
[_pages removeLastObject];
[self updateContentSize:_pages.count]; // this has a compiler warning
// I added this method to NSObject via a category so I can pass primitives with performSelector
// [self performSelector:#selector(updateContentSize:) withInt:_pages.count waitUntilDone:NO];
}
}
Ok, I have the following very simple animation composed of 25 frames in PNG format. Each frame is 320 × 360 and about 170Kb in size. Here is the code I use
.h:
IBOutlet UIImageView *Animation_Normal_View;
In Interface Builder I have a UIImageView with a referencing outlet pointing to this. All my images are named normal_000_crop.png, normal_001_crop.png, normal_002_crop.png,...
.m:
Animation_Normal = [[NSMutableArray alloc] initWithCapacity:25];
for (int i = 0; i < 25; i++)
{
[Animation_Normal addObject:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"normal_%03d_crop.png", i] ofType:nil]]];
}
Animation_Normal_View.animationImages = Animation_Normal;
Animation_Normal_View.animationDuration = 1; // seconds
Animation_Normal_View.animationRepeatCount = 0; // 0 = loops forever
[Animation_Normal release];
[self.view addSubview:Animation_Normal_View];
[Animation_Normal_View startAnimating];
On the simulator everything loogs good visual animation start as soos as the startAnimating is issued.
But on the iPhone 3G running iOS 4.0.2, the visual animation starts a good 2 to 3 seconds after the startAnimating is issued.
I have tried about every technique on I could find in blogs or forum that should solve this to no avail.
Any hints appreciated even if it's a completly different way to to a PNG based animation.
Thanks.
This is a good question and I will address it here with a few thoughts.
First, you are loading a series of graphics that are around 4MB in total size. This may take a moment, especially on slower (older) devices.
In the #interface block of your .h file you may want to declare two properties such as:
IBOutlet UIImageView *animationViewNormal;
NSMutableArray *animationViewNormalImages;
The first is the UIImageView that you already have (just renamed for best practices) and the second is a mutable array to hold the stack of images for the image view. Let me state that if having "normal" implies state. For clarification, are you loading additional sets of images for different states?
In your .m file in the #interface create the following method:
- (void)loadAnimationImages;
This will provide the function to lead the image stack to the mutable array defined in the header.
In the same .m file in the #implementation you'll want the following:
- (void)loadAnimationImages {
for (NSUInteger i = 0; i < 23; i++) {
NSString *imageName = [NSString stringWithFormat:#"normalCrop%03u", i];
UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
if (image) {
[animationViewNormalImages addObject:image];
}
}
}
As you can see I renamed the PNG files from normal_%03u_crop to normalCrop%03u as it is best practice to put the index label at the end of the file name (also most apps will output the content this way). The loop loads an image, checks to see that it is an image and then adds the image to the "image stack" in the mutable array.
In the init() you'll need the following:
- (id)init {
...
animationViewNormalImages = [[NSMutableArray alloc] init];
...
}
This allocates the (animationViewNormalImages) mutable array to hold your stack of images for the image view.
We'll now move on to the code for the viewDidLoad():
- (void)viewDidLoad {
[super viewDidLoad];
...
[self loadAnimationImages];
[animationViewNormal setAnimationImages:animationViewNormalImages];
[animationViewNormal setAnimationDuration:1.1f];
[animationViewNormal setAnimationRepeatCount:0]; // 0=infinite loop
...
}
We load the stack of images into the mutable array then set the properties of our imageView with the image stack, duration and repeat count.
Next in the viewDidAppear() we start the image view animating:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
...
[animationViewNormal startAnimating];
...
}
Once the imageView is animating as an infinite loop we need to handle when leaving the view in the viewWillDisappear():
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
...
[animationViewNormal stopAnimating];
...
}
Last (which should be the second thing we add the .m file) we cleanup in the mutable array in the dealloc():
- (void)dealloc {
...
[animationViewNormalImages release];
[super dealloc];
}
This is how we handle it and works for us, but then again, we're normally not loading 4MB of images into memory to animate.
The .PNG files are compressed when building the app and I am not sure if they are decompressed on the fly when loading the images our of the resource bundle. This is a boolean value int he Build Properties Build Settings (COMPRESS_PNG_FILES).
For performance you may want to consider the following:
Mark opaque views as such:
Compositing a view whose contents are
opaque requires much less effort than
compositing one that is partially
transparent. To make a view opaque,
the contents of the view must not
contain any transparency and the
opaque property of the view must be
set to YES.
Remove alpha channels from opaque PNG
files: If every pixel of a PNG image
is opaque, removing the alpha
channel avoids the need to blend the
layers containing that image. This
simplifies compositing of the image
considerably and improves drawing
performance.
Furthermore, you may find it's better the make one large image with all 24 frames (offset by the width of the individual frame) and then load it once. Then using Core Graphics with CGContextClipToRect then just offset the image context. This means more code but may be faster than using the standard stack method.
Lastly, you may want to consider is converting the .PNG files into .PVR (PVRTC) files. More information can be found here: Apple Tech QA, Apple Docs, and Sample Code.
I hope this helps and please vote it up if it does.
Best,
Kevin
Able Pear Software
imageWithContentsOfFile: tends to take a long time to process, especially if there are lots of files (25 is kind of a lot) and/or they're big.
One thing you can try is to switch it out for imageNamed:, i.e.
[UIImage imageNamed:[NSString stringWithFormat:#"normal_%03d_crop.png", i]]
imageNamed: is generally much faster, but tends to cache images more or less indefinitely.
If loading the images into memory and keeping them around throughout the whole app is unacceptable, you may need to do some tweaky things to load them in at an appropriate time and to unload them after they've been used. That stuff is always tricky, and requires multithreading to not block the main UI while loading. But doable. And there are examples.
Load those images before using the start animating method . I advice an easier way " Just call start animating method in the applicationDidEnterForeground . When calling this one don't forget your UIImageView's alpha property . If you set uiimageiew.alpha=0.01; like this , your start animating method will be called and the user can't see this animation " so there will be no lag anymore.