So a little while back we wrote about the confusing crashes on the device that we eventually figured out were caused by [UIImage imageNamed:] running out of memory when it tried to cache lots of big images. So we apparently solved the problem by making a thumbnail-sized image set and caching those, some 2.9 meg worth, whilst loading in the full sized ones uncached and only as needed.
Well, apparently turned out to be not good enough. See, it’s a few days later and we’ve finalized the design and got everything implemented and all seems to be good … except that it gets terminated without warning sooner or later. And quite often, Springboard terminates as well. Absolutely no correlation to any action or sequence in the program, no leaks, no out of bounds memory accesses, memory usage of the program barely a pittance. Yet, somehow, look at the console and you see system memory warnings scrolling by, you see it quitting background processes, and eventually quitting Springboard and/or the active application. Whilst that active application hums merrily along in blithe ignorance of the system crashing to the ground behind its back.
So apparently not only is 2.9 meg of images cached with +imageNamed enough to bring the iPhone OS to its knees, it’s not smart enough to, you know, actually do anything about it, like oh I don’t know, empty the cache or something?
It’s not like this is hard or anything, you can replicate the caching functionality precisely by declaring yourself an NSMutableDictionary *thumbnailCache and populating it like
- (UIImage*)thumbnailImage:(NSString*)fileName
{
UIImage *thumbnail = [thumbnailCache objectForKey:fileName];
if (nil == thumbnail)
{
NSString *thumbnailFile = [NSString stringWithFormat:@"%@/thumbnails/%@.jpg", [[NSBundle mainBundle] resourcePath], fileName];
thumbnail = [UIImage imageWithContentsOfFile:thumbnailFile];
[thumbnailCache setObject:thumbnail forKey:fileName];
}
return thumbnail;
}
and if you do get a low memory issue, just [thumbnailCache removeAllObjects] and you’re good. But you know what? After replacing the various +imageNamed calls with this … not a single quibble anywhere, the whole 2.9 meg worth of cached thumbnails go right in there and not a single problem for, literally, hours.
(Not that there were any problems after those hours, we hasten to add; simply that, you know, if you think about it, there probably really isn’t a lot of point extending testing of an iPhone application much past the lifespan of a full battery charge…)
So the moral of the story is: DO NOT USE [UIImage imageNamed] for any significant amount of images. It is EVIL. It WILL bring down your application and/or Springboard, even when your application is putting along using just barely a nibble of memory on its own. Take the handful of lines above and implement your own cache!

Problem with imageWithContentsOfFile is that it seems to be 2-3 times slower than imageNamed. (loading in 80 or so thumbnails) 1.7 seconds is now over 5 seconds on the device.
What OS are you running. Apple claims 2.2 is supposed to be better for cache releasing.
Really? I load in 368 thumbnails max as the user scrolls, and if it is slower it’s imperceptibly so. But maybe that’s just a usage artifact. Perhaps you could try threading your image loading then?
And no, all my observations that imageNamed will kill Mail and kill Springboard and kill your app without ever releasing images or asking you to do anything are all with 2.2. I stumbled across some discussion that it is believed that this is designed behaviour on imageNamed’s part — that it’s meant only for caching images that Absolutely Must be available to present a functional interface, and crashing is preferable to releasing anything loaded that way.
I ran into the exact same image while loading up background images (for use in animation) while working on the “Fairies Fly” game for Disney. I tried to run down “imageNamed” with some bugs and background loading as well, which is where I first ran into the incredible amount of caching that happens under the covers with it.
The download of loading from file yourself is that your device can take a bit to load a large number of images, meaning you need to carefully deal with all that in the background to avoid the “apparently frozen” issue that you get otherwise. Not insurmountable, but damned annoying anyway.
I never did find a good lazy-load ready setup for image use – ended up (like you) dealing with caching and loading all on my own.
I too ran into this issue and ended up manually caching things as well.
Thank you, thank you, thank you! With a release deadline very close, QA began telling me about random, irreproducible crashes when they simply banged on my UI continuously. They told me flatly “It will crash. Sometimes in ten minutes, sometimes in an hour.”
So I looked at the logs, and they all ended with an exit status of 101. And my didReceiveMemoryWarning methods were logging. It had been awhile since I googled “exit status 101,” so I did, and found your article pretty quickly. And I said Well, look at that.
Yesterday I implemented your solution throughout my code, and today QA sent an email saying they had been flogging the UI for six solid hours without a hiccup!
Did I say thank you already? Thank you.
You’re very welcome, David, glad I could be of help, and good luck with your project!
Thanks very much for this Alex! I’m wrapping up development of my first app and have been tearing my hair out trying to find out why my relatively simple and trivial app has been crashing so much and running into memory problems constantly. And, guess what? It turned out to be all thanks to imageNamed!! I found that replacing all imageNamed calls with initWithContentsOfFile caused by table views to grind to a snails pace but upon implementing a version of your caching code above, all my problems have been solved!! Not only is it now stable as a beast but overall performance has improved massively as well!
And you’re most welcome too John. I’m glad to see all you people being saved my trouble sussing this out!
I’d argue against avoiding it altogether, I think it really depends what you’re planning on using it for. As long as you’re aware the method is caching images you should be able to make an educated decision about a few 16×16px icons that are always going to be displayed, versus a 800×600px picture the user will only see once.
Implementing your own cache you can empty when you need to is a good idea though, I’ll have to keep that in mind.
I hate to ask the obvious, but any updates to the imageNamed fiasco for 3.0?
Alex – thanks a ton for this post, you saved me some serious heartache trying to track down my app crashes. One of the things I’m going to play with next week is combining the caching idea here with some of the url based background loading ideas I’ve seen out there. If it’s done right we should be able to have seamless large image loading/caching.
Hello,
Has anyone got this working with a UIImageView for animating a large number of
large images (say 200px by 120 px?)? I’ve implemented the imageCache as a way
of trying to fix a UIImageView animations problem. However when I run it the
3G iPhone, the device still becomes unresponsive. I can see the appDelegate
getting the memory warning, and clearing the imageCache.
I used it like so:
NSMutableArray *arrayOfImages = [[NSMutableArray alloc] initWithCapacity:53];
for (int i=1;i<53;i )
{
NSString *filename = [[NSString alloc] initWithFormat:@”introd.png”,i];
// Retrieve the image
UIImage *savedImage = [[ImageCache sharedImageCache] imageForKey:filename];
// Add image to array
[arrayOfImages addObject:savedImage];
}
self.myImageView.animationImages=arrayOfImages;
[self.myImageView startAnimating]
@Paul:
That’s on the order of 100K per image. Couple dozen of those and you’re going to start running into iffy memory territory, your own cache or not. Instruments would be of use in detecting what’s actually going on.
There seems to be a lot of FUD around imageNamed. Does anyone have any real knowledge of perfomrance particularly under low memory conditions?
I’ve created a SO question for any answers.
r
There will be two groups of people that find this web page. Those that are just loading images for static display purposes and those that are loading images for purposes of animation (or should I say that it will be people with problems doing one of these two things).
For those that are simply loading images for static display purposes this might help you to manage your memory more effectively. However, this is NOT because this code is caching anything because it isn’t (at least not in the way imageNamed is), but simply because it is not using imageNamed (simply because it is NOT caching is why it works). The same effort can be accomplished by simply using imageWithContentsOfFile (or any number of other methods) and tossing the images into an NSMutableArray (or better yet storing the CGImageRef in an array). The point is that this code is NOT a substitute for the caching that you get when you use imageNamed. It simply avoids using the caching at all and as a result you don’t get the memory problems (but you could get other problems…see below). So, for those that are simply displaying static images the advice is simple. Don’t use imageNamed.
Now, for the rest of us (and I would say the majority of people that read this webpage). This code will not help you at all with the animation. It is not really caching anything (at least not the way imageNamed does it). If you are like most people you are animating just fine with imageNamed, but after a while you get a memory error because imageNamed is caching all your images even if you disposed of them (they still sit there in memory). So, you search the web and you find all sorts of advice that tells you not to use imageNamed. So you do that only to find that your animation crawls when you use something else. Although you have gotten rid of your memory error by avoiding the caching you have introduced a bigger problem by slowing down your animation to a crawl. The above code is no different. The above code is simply using a dictionary instead of an array to store the images, but it is the same thing (nothing is really cached). So, I am afraid to say that this solution will not help anyone that is having trouble animating their images.
I should mention that this is not a problem if you are simply animating a few images or small sized images. So long as you can store all the images in memory then you won’t run into this problem. Its just that at some point you might have too many images or your images are too large (it doesn’t take much). At that point using imageNamed is not going to work, but then what else can you do but use imageNamed if you are animating?
And here is the sad part. I got your hopes up, but I don’t have a solution. I haven’t tried to tackle such a problem yet. Maybe I will mess with it a bit and see what I can come up with.
Still, I suspect that you might have to move to using OpenGL or something, but I am not yet willing to give up on some simple solution. I was thinking that you might be able to load the images onto CGLayers (or UIView’s/UIImageView’s if you don’t want to mess with CGLayers) and then simply bring the layers into view to do your animation.
Since you would not be using imageNamed, but rather use something else (mentioned above) then you will avoid the memory problem. Also, since you are both loading and displaying the images during the load of all your animation frames you should also be able to avoid the animation crawl problem.
Of course, this will likely slow down your animation images overall load time. But that is probably a small price to pay to be able to avoid both the memory problem and the animation crawl problem. You would likely have to add a progress bar (by the way, you will shot yourself trying to add a progress bar…hint…use multithreading).
Anyway, I will try to throw together some test code for the CGLayers idea and let you know if it works. Don’t really have the time right now though. If someone else gets it working please let us know.
Paul,
Let me list off a few pitfalls/ideas/thoughts.
1. Figure out what your seconds per frame is. You didn’t list your animation duration so I can’t figure it out (in your case it is 53/duration). That value will give you some idea of how long you have to display each frame. If you take longer to display a frame than the time you have given yourself to display a frame then you have a different problem altogether (or I should say you have an additional problem to the ones I have already mentioned). The timer messages will start to back up and the OS will shut you down and/or your animation will be slow and/or choppy.
2. Do not resize your images. If you are loading an image that is 300×300 then put it into a CGRect that is 300×300; otherwise, your animation will slow down to a crawl. It is possible that if you are currently resizing your images that you can solve your problem by simply not resizing them. Though I suspect that such is not the case given the sizes of your images. Still, you will have no hope of solving the problem if you are resizing your images (intentionally or unintentionally). So, do not resize your images.
3. Scrap the animationImages approach altogether and instead create a timer that triggers the displaying of each frame. Actually you display the frame in your drawRect, but in the timer you will increment some current frame variable (an index into your images array) and do a setNeedsDisplay to cause drawRect to fire (so the timer will only have a few lines of code). The drawRect will then simply draw the image that is indexed with that current image variable (some int). As I understand it, animationImages has some overhead that doesn’t allow you to animate frames as fast as you can do if you do it yourself; however, that is not why I suggest you use a timer. It is likely that you cannot solve this problem if you are not able to write the animation code for yourself. For example, if the solution is to use OpenGL or to swap between CGLayers (or UIImageViews) then you must use a timer in order to do such.
4. Use imageNamed until you know that you cannot use it. This is to say that if it is possible for you to use imageNamed without running into the memory problem then that will be your best chance at animating your images fast (because they are all cached). In such a case, if your animation is slow then it is likely due to some other reason (like you are resizing or something). You can speed things up by manipulating your PNG files in Photoshop. In the end, if you are not having a memory problem and yet you are still having a problem with slow animation then you are not giving each frame enough time to display. You have to cut. Cut out frames, cut down the image dimensions, cut out colors, cut cut cut. Or you can increase your animation duration, but you have to do something to get your “frame display time” down below your “seconds per frame time”. Of course, if you have a memory problem then you can’t use imageNamed and thus have a different problem altogether.
5. I suspect that some settings in your PNG files slow things down a bit. Like I think interlacing slows things down, but there might be other PNG properties that should be considered (use something like Photoshop to manipulate your PNG’s).
6. You might want to use CGImageRef instead of an array of UIImage’s, but it really won’t have much effect on the animation times. So, either way is fine. Though I think the dictionary idea kinda confuses things (particularly if you name it with the word “cache”). It’s probably easiest just to use an array (mutable) or even a static array of CGImageRef’s (like CGImageRef myFrames[50];). However, it’s all the same stuff really. If you are comfortable using a dictionary then that is fine.
7. In the end you will likely need to come up with some more creative way to push the displaying of your images into your loading time. But you gotta do it in some way that doesn’t make use of the same caching that imageNamed does (this is what I fear will happen even if we use CGLayers or UIImageViews to display all the images during our load time–it might still cache the images and then the memory problem will occur again).
Hope this is at least a bit helpful. You can search for code that does most of this stuff online (like animating using an timer) so I won’t post any here until I actually get something working for myself.
Ahhhh…I made a number of typos in those two messages. Sorry for the typos, but in particular I wanted to point out that it should have read “duration/53″ for your seconds per frame calculation.
For example, if I was to guess I would say that a 200×120 image would average about 0.008 seconds to display (provided it is loaded with imageNamed–this is just an educated guess). However, I’d say that that display time could get up to as much as 0.03 or maybe higher (it varies due to what is going on on the iPhone at any given moment). Again, these are just educated guesses so I can illustrate my point (you can measure these actual times if you use drawRect and a Timer, I would probably do so just to be sure of what the limits of my images are).
Anyway, if your animation duration is 1 second then you would have only 0.018 seconds to display any given frame (given that you have 53 frames to display). I think that would be a problem if a number of your frames fall above 0.018 seconds to display. If that is the case then you would need to do something to give each frame more time (like reduce the number of frames or increase the animation duration).
Mind you, this is not normally a problem since the fps of NTSC TV is about 23 fps and looks good enough. That means that you really only need to animate at 23 fps giving you 0.043 seconds per frame as a lower limit. There is plenty of elbow room in that time. So, if need be you can reduce the number of frames you are animating so long as you don’t go below 23 per second (though you might want to use the PAL standard of about 29 fps–or something in between).
Of course I am only guessing about your animation duration (you did not tell me), but whatever it is you should be thinking about it as I have just done here. What is your fps? What is your seconds per frame? How long do you suspect it should take to display a UIImage on the screen? Does all your figures work out correctly?
Once you get your Timer added to your code you can then test out how long it actually takes to display each of your images. Then so long as you can keep above those times you should be able to animate your images properly.
Note, that I am ignoring the memory problem in this illustration. However, even if you end up having a memory problem and cannot use imageNamed you still would need to consider all these timings (fps, etc) in whatever solution you eventually ended up with. So, if you have a memory problem then you can’t use imageNamed and instead you use CGLayers or something. Well, you still need to be able to make sure you can switch between layers faster than the time you have given for each frame to be displayed (seconds per frame). So all of this stuff still comes into play (it still needs to be considered no matter what approach you use).
Opps, lots of typos. CGLayers should obviously be CALayers. There is too many for me to fix. Most of the typos are easy enough to figure out what I meant. Sorry about that.
Ok, using CALayers and UIImageViews is still slow at displaying (animation is still slow). I tried swapping layers, manipulating the Hidden property to display the current layer, and playing around with the zPosition property to bring forward the current layer, but it still slows down the animation (playing with zPosition was the most promising). Nothing gets close to the speed of using imageNamed (at least not yet…I still a few more things for me to try).
Paul,
For my tests I generated sets of 40 images of sizes 300×300 (about 10 sets). I found that the code doesn’t really mess up until I have loaded more than 4 sets. Loading one set was never a problem. So, I suspect that if you only have one set of 53 images of size 200×120 you should probably not be having any problem even using imageNamed. This means that your problem is most likely with the way you are displaying the images. It probably means that your duration is too fast for the number of frames you are attempting to animate or that you are resizing your images during the animation (intentionally or unintentionally).
Also, as I said, using animationImages might have some additional overhead that would limit how fast your duration can be. You should probably use a timer as I suggested.
In any case, let me know your Duration of your animation and I can tell you if that is your problem or not.
So far I think the conclusion is as follows:
Use imageNamed if you are not running into any memory problems. Provided you are not doing anything massively stupid like creating huge images or images with massive colors then you should be OK using imageNamed for most projects. In my example I used multiple sets of 40 images of size 300×300. My example loaded only 40 images at a time (properly coding the disposal of the previous 40), but since I was using imageNamed it was likely that the previously loaded images were not removed from memory (the source of all this “imageNamed is Evil” thinking).
Doing this I was able to load (and dispose) of a number of sets before I ran into any memory issues. From this you can get some idea of what the capabilities are for imageNamed. If you just loading 50 images for an animation then you should NOT have a problem (Paul would likely fall into this category, his problem is likely related to something else–which should be good news for Paul), but if you are loading sets of 50 images and you hope to get 10 or 20 or even 30 sets into your application (or if you wanted to load hundreds of images for a single animation) then you will likely have a problem (if you use imageNamed). In the latter case you would NOT be able to use imageNamed, but in most cases using imageNamed is the way to go (mainly because I don’t yet know of another solution for animating without it…at least not yet…I do have an idea that might just work without using OpenGL).
And don’t assume that because you are running into memory problems that it is a problem with imageNamed. It is more likely a problem with your disposal of your temporary objects within the loop that loads your frame images. Using imageNamed may result in a memory problem, but that doesn’t mean it is the culprit in ever instance. For example, Paul is loading 53 images of size 200×120. From my tests (loading 40 images of size 300×300) I would probably say that the problem is NOT due to imageNamed in his case. Sure it might be, but it is NOT the first thing that I would suspect. So, don’t assume the problem is with imageNamed until you are sure it isn’t due to some other memory usage in your code.
Ok, that will have to due for now. I will try to find time to test out the other idea I have (I am a little exited that it will work), but I probably won’t get to it right away. When I do I will post back here.
Oh I did want to give you some figures that might help you.
For my 300×300 optimized PNG images the display time is on “average”:
0.008 to 0.01 second
Interlacing slows the display down and so does using more colors, but keeping the images looking good (high enough colors) the above is the range of average display times.
The range of maximum display times is:
0.012 to 0.024 seconds
Note that sometimes you might see a time that jumps way up outside of this range, but so long as it is not a frequent occurrence it should not mess up your app. The message will just wait on the message queue, but it will still get processed faster then the user will be able to notice (again, provided such hiccups don’t happen often).
So, if I was writing an animation app using 300×300 images like the ones I am using. I might want to give each frame as much as 0.028 or 0.03 seconds to display (that is, the seconds per frame should be in that range), but I could probably go down to as little as 0.024 and get away with it.
So, lets say I was happy with 0.024 seconds per frame (though I wouldn’t be comfortable with it being so close to the actual display time, maybe a 10%-40% buffer would be nice). That would mean that I could display those frames at a maximum of 1 / 0.024 or approximately 42 fps. Well, 42 fps is much higher then I would need (as I said TV is about 23 for NTSC or 29 for PAL). So, this would be great news if I was writing such a program. At say 25 fps I could get away with 1 / 25 or about 0.04 seconds per frame (giving me a buffer of over 40% of my actual image display times–that should be good enough for any hiccups).
Anyway, this is real data that you might be able to use to gauge where your animation problem may be. If your images are similar size to mine and you are trying to animate over 42 fps then you will definitely have problems. Even if you are animating at 35 fps you might have problems. Heck you could even theoretically have problems at 25 fps, but the likelihood goes down drastically as you move downward from 42 fps (again, if your images are similar to my test images). You could do all these calculations for yourself and then you can know for sure what is going on in your code.
The point is that once you know what is going on with your fps, seconds per frame, and display times (maximum being important). You have a huge amount of wiggle room to adjust things to get your code to work just fine.
Let me just clarify something. In the above example I am working with maximum display times and I am saying that I would be comfortable working at 10%-40% above those maximums. However, this is just my comfort zone. Even if you went below the maximums to achieve higher fps you would likely be OK. It kind of depends on the animation. It just a matter of how much you can get away with before you don’t like the looks of the animation anymore. The point is that if you go below the maximum display times then your timer messages will start to queue up and things will start to look more and more sloppy.
Actually, its a little stranger than that since I suspect that the message queue is a priority queue. So, your timer messages might get processed first before your drawRect messages. Remember the timer functions do not take long to complete as they simply increment a current frame counter and fire off a message to drawRect. Its the drawRect that takes the time. So, what you might get is drawRect messages stacked on the message queue. I haven’t really thought it through exactly, but the point is that some type of messages will queue up and the result is that it will effect the flow of your animation (choppy, sloppy, jumpy, maybe slow, lagging, skipping, going up a hill to fetch a bail of water). How much the flow is effected MAY be within the tolerances of the average users (viewers), but at some point it will NOT. So, you could probably go down below the maximum display time to some point, but where that point is is subjective. My point is that if you keep above the maximum display time you don’t really have to care about such things.
So, because I say that I would personally be comfortable with 10%-40% above my measured maximum display times, I am just saying that that is what I am comfortable with. You can still go below those times to some point and still produce a nice animation. You would know that some messages are being queued, but you would also know that that is OK since you are managing the damages of such message queuing.
By the way, what I actually do is calculate the display times for ever frame and that way I can get an idea of the distribution of display times. The standard deviations might give me some idea of how far below the maximum display times I can go (as a rule of thumb) before I might start to run into problems (though I would just eyeball it myself…can’t be spending time figuring the standard deviations). Still, the problem I see with this kind of thinking is that it might run fine on my new iPhone, but how might it run on an older model iPhone or on an iTouch. Its just easier for me to try to give myself as much of a buffer as I can and keep my “seconds per frame” above my “maximum display time”.
I find it is not too hard to get it even to about 40% and still have quality images. So, I don’t think there is a need to push things. Thus I might recommend that you keep your frame times above 10% of your “maximum display times”. But it is up to you what you are comfortable with.
Ok, I’ll shut up now. I think I beat this subject to death (at least for now).
Many thanks for this tip!! Helped me a great deal. Yours was the first post I hit upon when I faced this issue and boy what a perfect solution you’ve given… fantastic! many thanks again!!
I regularly bash my head against the desk over these issues outlined here. I am considering dropping imageNamed. I have a large map image & imageNamed causes no end of grief for me & my users.
After some quick tests. Yes. using imageWithContentsOfFile does load slower; however using this approach my memory footprint drop by no less than 10Mb immediately! Memory spikes also recovered quickly.
And, Once you’d scrolled around the CATiledLayer a bit and zoomed in & out there was little difference. There seems to be some magically massive overhead with using imageNamed with large images. And when that caching goes wrong, the only fix is a complete shut down & restart of the device to clear it.
Now mulling wether or not inflicted the cost/benefit on my users.
Thanks a lot for this code Alex, it REALLY improved the performance of my (first) app.
Using it in a UITableView which updates on demand and it really saves a lot of memory, compared to imageNamed: – even the performance is better!
I have rolled out version 4.0 of Tube Deluxe using imageWithContentsOfFile instead and it has significantly improved the stability of the Map outta sight. Instrumenting the memory usage, it essentially hands it over to springboard to render so the magical disappearance of 10Mb I reported before doesn’t hold true. But it sure is more stable.
I still use namedImage for many other icons etc. I am so suspect of the system cache, this is what things like CATileLayer are supposed to exploit, but it’s just not stable for large image use (png’s), core graphics CATileLayer doesn’t seem to suffer the same fate.
Hi Daniel Alexander, Alex
I have 60 Images that I am animating. Each image is about 3K in size. I have used UIImageNamed which crashes very predictably. Using UIImageWithContentsOfFile works, but the initial animation loop struggles and stutters, after which the playback is great. I dont have any memory issues now. But, I wonder why my initial animation(the first time I show the 60 images animating) loop struggles. I have already loaded the 60images as UIImage objects in arrays before i start the animation. The first loop struggles while all loops after that seem to playback fine. Any ideas why? And how I can solve it?
Bbza
@Bbza:
Are you using UIImageView’s -startAnimating and -stopAnimating calls? If not you might try that and see if it does all the appropriate caching before starting. If not I don’t have any other immediate susggestions, sorry.
In my test I used sets of 40 images that were of dimension 300×300. The PNG file sizes were about 30 KB each. I generated 10 sets of 40 images and started to run into trouble after loading the 4th set. So, I would think that 60 images of size 3 KB should work with ImageNamed. I suspect that any problems you are having with ImageNamed is due to something other than using ImageNamed. So you should probably review your animation code to make sure it is functioning properly.
I should be careful here since you didn’t tell me what dimension your images were. Its not so much the file size that matters as it is the dimensions of the image. The image will be loaded and then it will be decoded so that it can be displayed on the screen. The decoded image size will be much larger than the PNG file size. It is this size that really matters and it is dependent on the actual image dimensions (not the dimensions of the object you are putting it into, but the actual dimensions of the PNG).
The stuttering or slowness of your animation using WithContentsOfFile is due to the fact that you are using WithContentsOfFile. You should expect shuttering or slow animations if you use anything other than ImageNamed (thus the paradox we are all complaining about). As I understand it, using ImageNamed decodes the PNG to be displayed during the loading of the file, but other methods do not decode the image until it is being displayed (they just load the PNG). Thus ImageNamed is faster at actually getting the image onto the screen than the other methods because it simply displays the already decoded image where the other methods still have to decode the image each time it is displayed. Mind you using some of the built in animation methods will do some of this for you as well (so what Alex is saying is also worth a try).
I should mention that I wrote all the above messages and did my testing on V2.x the Dev. After V3.x came out I did do a bit more testing and Apple has made a number of changes that relate to this issue, but they really didn’t fix the problem. They just made it a bit different. So, there may be some differences that I have not tested, but in general the problem still exists.
Now there is one thing to also consider. If you change anything about the image before you display it. Like, if you have a 300×300 image that you load using WithContentsOfFile or ImageNamed and then you display that image into a 200×200 ImageView then that will force the image to be re-decoded and you will also see some of these strange effects (shuttering, slowness, etc). Also if you overlay multiple ImageViews it also causes such effects, as it probably requires some manipulation of the images (attempting to display only those portions that are visible). My tests simply displayed a set of 40 300×300 images into a 300×300 ImageView. Thus there was no re-decoding of the image after it loaded (by ImageNamed). When I ran the same code displaying the image into a 200×200 ImageView the animation slowed to a crawl. So, you lose all the advantage of using ImageNamed if you manipulate the image after it is loaded (like resizing it into a different sized ImageView).
In any case, you should consider using ImageNamed again, and review your code to figure out why it is crashing on you. I am surprised it is crashing, though as I said you only told me the file size of your PNG (I am assuming your image dimensions are also small, which is a lot to assume). What I would need to know is the dimensions of those images (and the dimensions of the object you are putting them into and whether you are manipulating the image in any way, or whether you are overlaying images, etc).
I am thinking that Apple had made a change to the way WithContentsOfFile is working (between V2.x and V3.x). I suspect that the image is loaded into memory without it being decoded (as usual), but then during the first time you display the image it is decoded. So, the first time the image is displayed it is also decoded at that time and the decoded image is then queued (thus it is slow the first time it is displayed). However, on subsequent animations the queued decoded image is used, and thus it only needs to display the queued image and no decoding is required. If this is true then that means that either they changed the way WithContentsOfFile works (as it did not do this when I tested it), or you are using some other method that takes care of the queuing of the decoded image (like what Alex was suggesting). I don’t know which, but I suspect that it is happening as I am stating.
In any case, however it is happening you simply need to get your code to decode the images after or during the initial loading. This means that you could do the animation (hidden) after you load the images or you could simply use ImageNamed (which will decode the images during the load). I have found that even when you try to do things in a hidden way (setting the Hidden property or hiding the animation behind another ImageView) it doesn’t seem to do what you want it to do. I haven’t tried it for this particular problem, but I did do some tests to try to solve my problem by toying around with hidden ImageViews and it didn’t work. I think that displaying an image onto a visible object is decoded differently than when it is being displayed onto a hidden object and as such it still ends up having to be re-decoded when you try to actually view the animation (hope you follow that because I can’t seem to explain it correctly). The point is that you are probably better off getting ImageNamed working. Still, I think that the above is likely the reason you are seeing this shuttering effect on your first animation. There is a decoding taking place during the first time you animate the images that is not happening during subsequent animations. The only way I know to get rid of that is by using ImageNamed.
I have to guess at a lot of stuff here so I apologize if I am rambling a bit. I suspect that you are already using the built in animation methods and it is those methods that are queuing your images. However, since you are using WithContentsOfFile the first time they are animated they need to be decoded. The time it takes to decode the images is substantial, so the first animation ends up being slow (shutters). After the first animation the images have been decoded and are queued (by whatever process you are using to display). Subsequent animations can then use the queued decoded images, and thus subsequent animations are fast and smooth (since they do not need to decode like the first animation did). This is a guess since I don’t have time to test it, but I think this is correct.
Unless you use ImageNamed, I don’t think there is any solution to your problem. If you use any methods other than ImageNamed then the images will need to be decoded before the first time they are displayed. That means that the first time they are displayed will take more time than subsequent times (provided the decoded images are queued by whatever method you are using…otherwise the images will always be displayed slow since they would always need to be decoded during displaying…but that is not the case for your code, so whatever you are currently doing is queuing the decoded images after the first displaying). Your problem seems to boil down to simply getting the images to queue before your first animation. The only way I can think to do that is by using ImageNamed (since it decodes during the load).
Still, please remember that any manipulation to the image during displaying will cause the image to be re-decoded even if you use ImageNamed. Thus even if you use ImageNamed you could still see some problems provided you are manipulating the images in some way.
Ok, that was a lot of iffy stuff from me. I hope something in all my gibberish was helpful to you.
The issue seems to be more that the decoding is required to decode _all_ of the images in the image array before the first frame is rendered, although by then, the UIImageWiew is smart enough to know that it should be on frame 8 or so.
I’ve tried splitting things up, and I can quite clearly see the startAnimating is blocking until the decoding is done. Heck, my button doesn’t even pop back up.
I’m doing this right now, as simple and stupidly as possible – load the iamges into an array during the view load. (they are 60 images, all 200×200 png files). I set my animation images, set the duration, set the repeat count, then hit startAnimating. which hangs for 1/2 a second, and my animation starts on frame 8.
I’ve tried a variety of things to fix this (some reasonable, some rather insane), and I’m at the verge of giving up and writing my own timer based code. Decode time for the the each frame should be around 0.5/60, (based on how long it takes for startAnimating to creak to life) or 0.008 (ie 1/10th of my frame duration), so I think I’ll still be ok.
Sigh. Giving us a simple and straighforward animation api is awesome. having it choke and burn the first time you try to run it, is not.
Dan.
Yea, I would just dump the built in animation. You can animate using a timer and you will have more control over it. All of my tests where done using a timer and not using any of the built in animation routines. Initially I tried using the built in animation, but most times I want to interact with the animation while it is animating (like stop an animation in the middle and do something different) and that is easier with a timer. Most importantly, using a timer allows me to actually time the drawling event. A lot of animation problems occur when your drawling routine takes longer than your duration (frame rate in the case of the timer). With a timer you can time the event to find out if you are giving it enough time for it to complete. The built in animation is good for simple on screen transitions or some such thing, but for any real animation (particular those involving user interaction) you should stick with a timer.
Now, I am a little confused over your duration settings. What you need to do is first decide on what fps you would be happy with. That might be something like 23 fps, because such will give you a smooth animation without being too fast. To simplify our calculations in this example lets just say you want to animate at 30 fps. From that your 60 frame animation should have a duration of 2 seconds. At this rate each frame would have 0.033 seconds to render on the screen. Now, for my examples I was displaying 300×300 images at an average rate of 0.008 to 0.01 seconds with maximum display times of 0.012 to 0.024. So, giving my images 0.033 seconds to display should work fine since even my maximum display times were faster than 0.033 seconds.
Ok, so what I don’t really know is what your durations are. In my above example I simply assume that your animations are 2 seconds in duration, but maybe you are using 0.5 seconds for your duration. If that is the case then that is where your problem is. For example, if you use a 0.5 seconds duration then your no longer animating at 30 fps. Instead you are animating at 120 fps. That would mean that you are giving each frame only 0.00833 seconds to display. The vast majority of frames would not be capable of rendering in that time. And that might cause all sorts of strange animation behaviors.
Now, the thing is you don’t need to animate that fast anyway. Of course, it is best to know that prior to generating your images because sometimes it is not easy to re-sample to a lower frame rate (you sometimes get a choppy result). So, if your duration is giving you frame rates that are above 40 fps then you should probably get your fps lower. You can do that by selecting ever other frame or every third or every forth frame. Less frames at the same duration will give you a lower fps and thus each frame will be given more time to render.
This is just a guess since I don’t know enough about your actual durations. Though if I am readying this right:
0.008 (ie 1/10th of my frame duration)
that would imply that your frame duration is 0.08. That would mean that you fps is 12.5 fps which is probably too slow. That would make your animation duration about 4.8 seconds. Still, if that is the case then that should definitely have been enough time to display each frame. So long as you are using ImageNamed to load your images then 0.08 seconds is just a huge amount of time to display a frame. Though if you are not using ImageNamed then that would be the problem. You need to use ImageNamed.
I’ve read all the posts so far about the animation issue. I too am having the same issue. My 185 image array is “stuttering” for about 1/2 second when it FIRST plays the animation. After than, it is smooth. Several suggestions have been given to create your own animations using a timer. How do you do this? Can someone provide some sample code to show me how to create an animation for an array of images using a timer?
Ok, first thing you will need to do is to load your images. I would load them into an common C array of CGImageRefs. Like this:
for (int i=1; i<=185; i ) {
NSString *cname = [NSString stringWithFormat:@"d.png", i];
UIImage *img = [UIImage imageNamed:cname];
myThings[i] = CGImageRetain(img.CGImage);
}
This assumes that your filenames are 3 digit png's (like 001.png, 002.png, … 185.png) and that all of the images are loaded into your app (there is some trickiness here in getting them loaded so that imageNamed finds them without having to reference them with the entire path, but usually it will default to the proper settings. If your images come up all black then that is because imageNamed is not finding them in the path.
Ok, so that should load your CGImageRefs into the myThings array. Remember that somewhere in your code you will need to release those references. Something like this:
for (int i=1; i 185) {
currentFrame = 185;
[myThingyTimer invalidate];
//[myThingyTimer release];
}
else {
[self setNeedsDisplay];
}
}
This essentially walks through each of your frames and triggers drawRect. When it reaches the last frame you will need to invalidate the timer. Now, you have to create the timer somewhere and put it on the run loop. This I can’t remember off hand so I will look that code up. I know the code, but I can’t remember if you have to release the timer and recreate it every time or if you can just put it back on the run loop after it has been invalidated. Oh heck, lets just release and recreate it each time. So, you might add a [myThingyTimer release] to the myThingyTimerFired code above where I put it in comments. Now, that depends on how you are doing things in your code. Basically you will need to invalidate and release the timer before you recreate it. In other words, lets say you want to have an animation occur ever time the user touches the UIView. So, each time the user touches the UIView you will create a new timer. After the animation completes you will need to invalidate and release the timer so that it could be recreated for the next time the user touches the UIView. Where that is done is a question that relates to what you are doing in your code.
Now, keep in mind that I think that you don’t have to recreate the timer. I think you could just create the timer once and simply put it on the run loop and invalidate it to remove it from the run loop. But I can’t remember so for this example I am just going to release it each time and recreate it.
So, here is the code to create the timer.
myThingyTimer = [[NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(myThingyTimerFired:) userInfo:nil repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:myThingyTimer forMode:NSDefaultRunLoopMode];
Just remember that you need to match up your timer release with this timer creation. If you repeat this creation code then you will need to have done a release in between.
Ok, we are almost there. The last thing we need to do is create a drawRect to put the images into our UIView. That might look like this:
- (void) drawRect: (CGRect) aRect
{
CGContextRef viewContext;
if ((currFrame > 0) && (currFrame <= 185)) {
viewContext = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(viewContext, 0, self.bounds.size.height);
CGContextScaleCTM(viewContext, 1.0, -1.0);
CGContextDrawImage(viewContext, self.bounds, myThings[currFrame]);
}
}
I think you need to flip the context so that the image doesn't get displayed backwards. So, I put that in there with the translate and scale. I can't remember exactly what that should be, but I think it is right. If your images are showing up backwards or upside down then it is those two commands that is doing it. Maybe comment them out or you could mess around with the values I used to get the image to be displayed properly. I think it is right though. Anyway, this will basically draw the image using the CGImageRef that you have in the myThings array at index currFrame. This function is called each time the timer triggers because you have a [self setNeedsDisplay] in your timer fire function (myThingyTimerFired). Also in the myThingyTimerFired function you increment currFrame so each time it triggers it advances to the next frame (image) in your animation. And that should do it.
You have to forgive me any typos, and my memory is not perfect so I may have made some mistakes above. I didn't get this from any code so it isn't tested or anything, but I think it should get you a good start.
One more thing. In this line,
myThingyTimer = [[NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(myThingyTimerFired:) userInfo:nil repeats:YES] retain];
I am creating the timer. I set the interval to 0.01. That might be too fast for your images. You didn’t tell me the dimensions of your images so I can’t say whether you can get your animations running that fast.
In any case, most of the problems are due to what duration you are using. I think you can get a decent animation if you animate as slow as 23 frames per second, but it all depends on what you want the animation to look like. That being said, to get your animation running at 23 fps you would have to use a duration of 1/23 = 0.043 seconds. This means that your animation would take 0.043 * 185 = 7.955 seconds.
If you kept the duration at 0.01 then that would mean you are animating at 1/0.01 = 100 fps. That is probably too fast. It is probably borderline, but it is definitely too close to your limit. Of course, your images could be tiny and then it might be ok. But this isn’t bad news. If you are trying to animate at this speed then you can cut your animation by 1/4 by selecting ever 4th frame and just using a 25 fps rate. It usually will look similar.
In any case, you should start your testing at a higher duration and work your way down to see where you start having trouble.
You know I think I saw some similar trouble when I tested out the changes between V2 and V3 of the iPhone SDK. What I noticed was that they fixed the memory overflow when using ImageNamed (to some extent), but it caused a bit of lag to show up at some point in the animation. So, whatever frames they are now releasing have to get reloaded and that causes it to lag the animation a bit. This was not a problem in V2, but it is a problem I noticed with V3. I didn’t test it as much as I would have liked. But this might be a new thread for us here (“imageNamed in V3 SDK is more evil”). The solution might be to simply reduce your image size (actual image dimensions).
Oh, and I think that this problem showed up if your image enters into the status bar area. This was a little strange. The status bar at the top of the screen can be hidden, but there is a little bug that doesn’t actually get rid of it. It is not visible, but it is still sitting there keeping you from capturing touches and such. Additionally, if you hide the status bar and then you draw an image into that region of the screen the image has trouble being animated. Drawing the image into that region effects the timing and sometimes gives you a choppy animation. It is the same thing as drawing other views over top of your animation view. If you overlay any view over any part of your animation view it will cause trouble with your animation timing. The same thing goes for the status bar. So, even if you hide the status bar it will mess up your animation if you place any part of your animation image in that region.
What else…Oh, I’m just repeating myself. That is everything I can think of. Don’t resize, don’t overlay other things on top of your animation image, don’t draw animation image into the status bar region (or off the screen for that matter), and don’t set your duration too fast. Even then you might have trouble with V3 SDK.
Hey, part of my messages are being chopped out. Like lots of my code message is lost. I’m not sure why that is. Let me try to post it again….here goes….
It won’t let me repost. Very strange. Let me break it up.
Ok, first thing you will need to do is to load your images. I would load them into an common C array of CGImageRefs. Like this:
for (int i=1; i<=185; i ) {
NSString *cname = [NSString stringWithFormat:@"d.png", i];
UIImage *img = [UIImage imageNamed:cname];
myThings[i] = CGImageRetain(img.CGImage);
}
This assumes that your filenames are 3 digit png's (like 001.png, 002.png, … 185.png) and that all of the images are loaded into your app (there is some trickiness here in getting them loaded so that imageNamed finds them without having to reference them with the entire path, but usually it will default to the proper settings. If your images come up all black then that is because imageNamed is not finding them in the path.
Ok, so that should load your CGImageRefs into the myThings array. Remember that somewhere in your code you will need to release those references. Something like this:
for (int i=1; i<=185; i ) {
CGImageRelease(myThings[i]);
}
Where you do this depends on whether you are switching out the 185 images for another set of 185 images. If you are just keeping them in memory for the running of your app then you would do this in the dealloc, but you need to do it somewhere.
Ok, so far the only iVar you need is the CGImageRef array, but you will also need some indicator of what frame you are animating. Let call it currFrame. And you will need some timer (we will talk about that shortly). So, your class definition will look something like this:
@interface myThingyAnimationClass : UIView
{
CGImageRef myThings[185];
int currFrame;
NSTimer *myThingyTimer;
}
//put whatever methods you create here
@end
Note that everything I am talking about so far will be contained in the implementation of this myThingyAnimationClass. The only thing outside this class for our simple example would be creating an instance of the myThingyAnimationClass and putting it as a subview of some other parent view (or whatever). And I should repeat at the time of the creation of that instance you will give this myThingyAnimationClass a frame that must be the same size as the actual dimensions of your images (it doesn’t have to be, but if it is not then you might experience some slowness to your animation due to the resizing of the images). This is worth repeating as it can be substantial.
Anyway, back to the myThingyAnimationClass. You will initialize currFrame to whichever frame you want to start at (probably 1). And wherever you initialize currFrame you probably want to trigger a call to drawRect (we will talk about that below). This is because you probably want your first frame to be displayed even though no animation is taking place yet. If that is not the case then you need to initial currFrame to 0 so that the first frame will show up when the trigger is fired.
And that leaves us with two last things to deal with. You need do draw a frame in drawRect and you need to trigger the call to drawRect (and the increment of currFrame) in a timer. We already added a timer to our class iVars called myThingyTimer. So, the first thing we need to do is to create a function to handle the timer. We will call it myThingyTimerFired. It might look something like this:
-(void) myThingyTimerFired:(NSTimer *)timer {
currFrame = currFrame 1;
if (currFrame > 185) {
currentFrame = 185;
[myThingyTimer invalidate];
//[myThingyTimer release];
}
else {
[self setNeedsDisplay];
}
}
This essentially walks through each of your frames and triggers drawRect. When it reaches the last frame you will need to invalidate the timer. Now, you have to create the timer somewhere and put it on the run loop. This I can’t remember off hand so I will look that code up. I know the code, but I can’t remember if you have to release the timer and recreate it every time or if you can just put it back on the run loop after it has been invalidated. Oh heck, lets just release and recreate it each time. So, you might add a [myThingyTimer release] to the myThingyTimerFired code above where I put it in comments. Now, that depends on how you are doing things in your code. Basically you will need to invalidate and release the timer before you recreate it. In other words, lets say you want to have an animation occur ever time the user touches the UIView. So, each time the user touches the UIView you will create a new timer. After the animation completes you will need to invalidate and release the timer so that it could be recreated for the next time the user touches the UIView. Where that is done is a question that relates to what you are doing in your code.
Now, keep in mind that I think that you don’t have to recreate the timer. I think you could just create the timer once and simply put it on the run loop and invalidate it to remove it from the run loop. But I can’t remember so for this example I am just going to release it each time and recreate it.
So, here is the code to create the timer.
myThingyTimer = [[NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(myThingyTimerFired:) userInfo:nil repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:myThingyTimer forMode:NSDefaultRunLoopMode];
Just remember that you need to match up your timer release with this timer creation. If you repeat this creation code then you will need to have done a release in between.
Ok, we are almost there. The last thing we need to do is create a drawRect to put the images into our UIView. That might look like this:
- (void) drawRect: (CGRect) aRect
{
CGContextRef viewContext;
if ((currFrame > 0) && (currFrame <= 185)) {
viewContext = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(viewContext, 0, self.bounds.size.height);
CGContextScaleCTM(viewContext, 1.0, -1.0);
CGContextDrawImage(viewContext, self.bounds, myThings[currFrame]);
}
}
I think you need to flip the context so that the image doesn't get displayed backwards. So, I put that in there with the translate and scale. I can't remember exactly what that should be, but I think it is right. If your images are showing up backwards or upside down then it is those two commands that is doing it. Maybe comment them out or you could mess around with the values I used to get the image to be displayed properly. I think it is right though. Anyway, this will basically draw the image using the CGImageRef that you have in the myThings array at index currFrame. This function is called each time the timer triggers because you have a [self setNeedsDisplay] in your timer fire function (myThingyTimerFired). Also in the myThingyTimerFired function you increment currFrame so each time it triggers it advances to the next frame (image) in your animation. And that should do it.
You have to forgive me any typos, and my memory is not perfect so I may have made some mistakes above. I didn't get this from any code so it isn't tested or anything, but I think it should get you a good start.
Ok, that seemed to work. So, just ignore that first coding post I did earlier. It doesn’t make sense with all the lines missing. Maybe it was just too long. Hope that helps.
Hey Daniel,
I really appreciate your help. I am trying your code example with the timer and will let you know how it goes. What you did makes sense, I just didn’t know how to get started with it. I hope this approach will work as I am getting tired and frustrated with it all. Thanks Again!!!
I typed more than I had too. It is not as hard as appears from what I typed. Put simply you need to create a subclass of a UIView. And most everything you need to do will be in that subclass. The iVars you will need will be your array of CGImageRefs, an integer called currFrame that tells drawRect which frame to draw during animation, and a timer to walk you through the animation.
The timer basically increments currFrame and causes the UIView to redraw itself (which means forces a call to drawRect). drawRect is then called by the system and inside drawRect you draw whichever frame currFrame is pointing too (indexing).
Thus, each time the timer fires you get a new frame drawn and that is the animation.
All of the code is pretty much listed out for you in my mess of texts above, but in general it should be pretty simple to get it going….
…get it going yes, but that doesn’t mean it is going to work. All the other stuff I am saying above is to give you some insight into where the pitfalls are. For instance, you need to make sure your images are not too large for your animation duration. If you are trying to animate 300×300 images at over 40 fps then you might run into trouble (particularly if you are also trying to manage user interactions with your animations). This is just one of many problems that you might run into…some of the problems have solutions…some don’t. If your application requires you to do something that the iPhone just can’t do (like, to exaggerate the point, you wanted to animate 300×300 images at 500 fps) then you won’t be able to do it. However, sometimes you can change your requirements a bit (like reduce the number of images you are trying to animate and thus cut your fps down to something more managable). All of the other stuff above is stuff that might help you to work out how you might get around some of these issues.
What you can know for sure is that you must use imageNamed if you are going to be animating. Short of that you have to figure out ways to get your images and code to balance between imageNamed sillyness and iPhone limitations (etc…). I would also say that you are going to also need to do the animating with a timer like I have above. Those are probably the two things you must do (imageNamed and timer). So, make that your minimum application and try to make it fit in with your data.
Hope that helps…