Environment:
I am creating a "photo mosaic" app, and I try to display 1024(32*32) pieces of small images(retina size->w:30px h:20px) on the screen same time. Which means on total, it is the same size as the full screen image size.
Issue:
I load 1024 UIImages, create 1024 UIImageViews, and add all of them to a UIView. When I scroll to this view, there is a big lag: test on iPhone4(iOS 5) and iPhone5(iOS 6). It's just appear on iPhone4, and on iPhone5 is fine. (Supposing iPhone5 have much more better CPU, so I think it is reasonable).
What I think:
Supposing all images have been already loaded from local dir in the memory(using method "imageNamed"), so I think the problem must be in the somewhere of the step display/render the images.
So any idea about it? Any, any idea will be helpful.
Thanks so much,
UPDATE
It is much better after I took the advice from #Antwan van Houdt . Here is the principle code:
-(void)updateCoverImageView:(UIImageView *)smallImage{
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0f);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.coverImageView.layer renderInContext:ctx];
[smallImage.image drawInRect:smallImage.frame];
self.coverImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Then you just set the alpha value of smallImage to zero, so the system won't render them. And the cover image will replace it. That works for the lag issue caused by displaying a large amount of UIView same time.
Is every image in its own view?
In either case, for such a huge amount of images you should probably consider drawing them in 1 view and if that drawing method is still too slow you could draw it once, render the view to an image and then simply draw that image as it's cached display.
There are several guides available from apple that deal with the performance of custom view drawing and stuff like it.. you should give them a read.
Not knowing the full details of your code, any thing like this, should be done on a background thread. I would read up on graphics contexts, grand central dispatch and then use both those concepts to create the resulting image in another thread and then get the resulting image and display it in the main thread.
Infact since you have many images which can all be processed in parallel, it can be done very fast using GCD.
If you can post the code, I can help you optimize it.
Related
This is not really a question about addressing a specific problem but more a request to be pointed in the right direction.
I am making an app where I am loading several images (saved as JPGs) onto a screen at the same time and this has to be the way it is, I can't unload any of them because they are all being shown at once.
I tried loading 30 images at about 800px*600px resolution naively thinking that it only loads the 'compressed' size of the pictures into memory (this being about 200 KB) - so a total of 6 MB? Of course, several memory warnings later, I realised how stupid I was. So i now re-size them to about 400px*300px each and my iPhone 4S just about copes with the memory requirements.
I originally used a UiView where in the drawRect I drew a custom drawn image but changing over to using a UIImageView improved the situation dramatically. The app is so much faster and responsive.
I also found that switching off rasterization in my layer made a huge difference in performance.
These sorts of 'discoveries' have taken me hours to work out and what I was wondering was if there are particular design patterns or resources that I can use to load my images to the screen as efficiently as possible; mainly regarding using as little memory as possible - are there are good rules of thumb? Why did using UIImageView vs UiView w/ image make such a difference?
Would be very grateful if someone could help.
Thanks.
Implementing -drawRect: causes the system to allocate a bitmap image of the same size as your view (after all, you need a buffer to draw in to). If all you're doing is drawing an image you've already loaded, then in one fell swoop you've doubled the memory usage for that image (because you have the copy you loaded, and the second copy you just drew).
Similarly, rasterizing layers requires allocating bitmap images the same size as the layer, so it has a buffer to rasterize into. So turning that on sucks up memory as well (proportional to the size of the layer).
The basic rule of thumb is, don't do extra work. Using -drawRect: to draw an image is extra work. Rasterizing a layer is extra work (though, depending on what the layer is, this may be a one-time performance cost (and a constant memory cost) in order to save on performance later, e.g. if it's a CAShapeLayer or if it's drawing shadows). Keeping large images in memory that you always scale down before rendering to screen is extra work (just scale it down once when you load the image, and keep the scaled copy around).
Another thing to keep in mind is, if your goal is to draw images, you should try to use UIImageView if you possibly can. It's generally the fastest and cheapest way to get an image to the screen, and it's reasonably flexible.
The design pattern is basically, use UIImageView. Apple has spent a lot of time making it fast, and Apple is allowed to use private APIs that you aren't.
That said, if you want to do it yourself, you should try using one CALayer per image. You just load an image and set it as the content property of a CALayer. The CALayer can cache its contents in GPU memory, and may do other optimizations that you can't do with public APIs.
You can learn a lot about making your UI fast by watching Apple's development videos. They include a lot of tips and "inside info" that are either not in the written documentation, or hard to find/easy to overlook in the docs. The development videos are here: http://developer.apple.com/videos/. Some good ones relevant to your question:
iOS - "Understanding iOS View Compositing"
WWDC 2011 - "Understanding UIKit Rendering"
WWDC 2011 - "Practical Drawing for iOS Developers"
WWDC 2011 - "Core Animation Essentials"
WWDC 2010 - "Core Animation in Practice"
I'm having some performance problems with images in my app. I assign a UIImageView to the backgroundView property of a UITableViewCell. The Time Profiler instrument tells me that I'm spending most of my time here:
My table view has semi-transparent cells to let the background shine through. I know this is not good. But from what I can tell from the Profiler, this is not the bottleneck, right?
png_read_filter_row sounds like if there is some kind of expensive filtering going on.
Also down the bottom, there are 10.6% spent on transform_premul_argb_fn ... which sounds like some kind of scaling. My images are not scaled at all. I use them naturally (i.e. if the display needs 200 x 100, it gets 200 x 100. If it's retina, it gets a 400 x 200 version).
And finally, 6.7% on gzopen which sounds strange. My images reside in the Documents directory, not in the zipped App Bundle.
Maybe someone with deep Core Graphics knowledge can tell what those calls mean and how to avoid them?
Make sure your images are uncompressed after loading. Use the following code from http://markmail.org/message/vav7a5khncak2u3h
UIGraphicsBeginImageContext(image.size);
[image drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1.0];
UIImage *decompressed = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I have about 20-ish high quality images (~3840x5800 px) that I need to load in a simple gallery type app. The user clicks a button and the next image is loaded into the UIImageView.
I currently use [UIImage imageWithContentsOfFile:] which takes about 6 seconds to load each image in the simulator :(
if I use [UIImage imageNamed:] it takes even longer to load but caches the images which means its quicker if the user wishes to see the same images again. But it may cause memory problems later with all that caching crashing my app.
I want to know whats the best practice for loading these? I'm experimenting with reducing image file size as much as is possible but I really need them to be high quality image for the purpose of the app (zoomable, etc.).
Thanks for any advice
[EDIT]
Hey again guys,
Thanks for all ye're advice. The project's spec's have changed a little. Now as well as displaying the images they firstly have to be zoomed in to a particular spot and when the user taps next it zooms out and then displays the next image. So I'm not sure if the proposed solutions fits?
Apple's docs recommend against trying to load single images that are larger than 1024x1024. You should look into using CATiledLayer instead, to load pieces of the images as needed.
You can have a look at this Apple sample:
http://developer.apple.com/library/ios/#samplecode/PhotoScroller/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010080
It shows how to load big images, breaking them in tiles for different zoom levels.
You can't see all those pixels at any given time, so there is no need to load them all. Load lower-res copies ("big-thumbnails") to view the complete image, then selected sub-tiles, maybe of 2 or more different resolution sets, after the user zooms in.
The CATiledLayer API may be able to handle some of the latter for you.
I'm seeing my app being killed by iOS with an out of memory message, however, while tracing the progress of the app in the Allocations Instrument, I see lots of mallocs that seem to be occurring outside of the code I've written.
I'm not seeing any leaks being caught, so I assume these allocations are supposed to be there. Thing is, because I'm not sure about why they have been allocated, I'm not sure what I can do to optimize the app and prevent the OS from jettisoning my app.
Does anyone know why the memory is being allocated, or is there any way for me to find out?
Here are a couple of shots from Instruments showing the mallocs. In the second shot, all of the allocations have the same stack trace.
EDIT
I' displaying a single large image as the UIView background (1024x768), then overlaying a smaller (600px square) UIView with some custom drawing and a third UIView (550px square) over the top of those that contains two 550px square images overlayed.
I'm guessing that this is not appropriate, and there is probably a better way of achieving the composition of views I need for the app to work.
Should this be possible on the iPad?
I think there's not really much information to go on here - if you add a bit more information about what this view in your app is doing you might get some more informed suggestions.
From the screenshot, it would appears large blocks are being allocated to display an image.
Given that I'd hazard a guess that either you're trying to display some very large images, or you UIView is large, or you have more UIViews in memory that you need to display the current screen.
I guess the easiest way to track down exactly where they're coming from would be to disable the part of the application you suspect then run again and see if the allocations still occur.
EDIT
Are all the images the same size as you're displaying them? (ie. are you trying to display a 5M photo as the 1024x768 background?) If not you probably need to scale them down to the size you are display them, or at least closer.
If you're not needing transparency, make sure to make all the views opaque.
I figured out the source of the problem - I was using
[UIImage imageNamed:#'Someimage']
to load in my images. This, as I'm sure many people are aware, caches the image data. I had enough images of sufficient size to cause my app to be jettisoned.
The problem was apparent not because of the size of the image but because of both the size and number of images I was using. The lesson here is be careful with [UIImage imageNamed:].
Thanks for all of the help, chaps!
Mallocs can occur inside of other API's that your app calls (such as loading images, views, playing long sounds, etc.) You can try changing the size of your images, views, sounds and other objects by various amounts as a test, and see if the size of the malloc'd memory changes track one of the changes that you've made.
I've got an app I'm working on where we handle a LOT of images at once in a scrollview. (Here's how it looks, each blue block being in image on a scrollview expanding to the right: http://i.stack.imgur.com/o7lFx.png) So to be able to handle the large strain doing this puts on memory. So I've implemented a bunch of techniques such as reusing imageviews etc which have all worked quite successfully in keeping my memory usage down. Another thing I do is instead of keeping the actual image in memory (which I of course couldn't do for all of them because that would run out of memory very quickly) I only keep the image's filepath in memory and then read the image when the user scrolls to an area of the scroll view near that image. However, although this is memory efficient, it's causing a LOT of lag in the scrollview because of the fact that it has to constantly read images from the disk. I can't think of a good solution on how to fix this. Basically right now the app draws to the screen only the visible uiimageviews and while the user scrolls the app will look to see if it can dequeue another imageview so it doesn't have to allocate another one and at that point it reads the image into memory, but as I said it's causing the scrolling action to be very slow. Any ideas on a strategy to use to fix this? Does anyone know what the native photos app does to handle this kind of thing? Thanks so much!
I can suggest you a simple solution to balance both the memory and the computer processing. You only keep small images like thumbnails in memory and only keep about 20 of them. One project that I am doing, I keep 20 thumbnail images (100 x 100) recently accessed, which doesn't cost a lot of memory. I believe that it costs about 200 kb all the time but comparing to a general available memory. I think it is good enough.
It also depends on your use case : if user scroll really fast and you don't know when will they go. You can have even smaller images than the thumnail and when you show it on the UIImageView, you resize it to fit. When user stops scrolling for a while. You can start loading bigger images and then you have a nicer images. User may not even notice about the process
I don't think there is a solution that can be fast and using as less memory as possible. Because we have memory, maybe not big but have enough if we use it smartly.
Slow scrolling performance might mean that you're blocking the main thread while loading images. In that case, the scrolling animation won't continue until the images are loaded, which would indeed cause pretty choppy scrolling performance.
It would be better to lazily load requested images in the background, while the main thread continues to handle the scrolling animation. A library that provides this functionality (among other things) is the 'three20' library. See the Tidbits document, and scroll down to the bottom where the 'TTImageView' class is described.
I had a similar issue with a PDF viewer, The recommended way to do this is to have as low a res image as you can get away with and if you are allowing the user to blow the image up/zoom, then have two versions or three versions of that image increasing the res as you go.
Put as much code as you can get away with in the didDecelerate method (like loading in higher res images like vodkhang talks about), rather than processing loads in didScroll. Recycle Views out of scope as you have said. and beware of autoreleased Context based Image Creation functions.
Load images in on background threads intelligently (based on the scrollView Offset position and zoom level), and think about using CALayer/Tiled Layer drawing for larger images.
Three20 (an open source iOs lib) has a great Photo Viewer that can be subclassed, it has thumbnail navigation, large image paging, caching and gestures right out of the box.