I have a tableView with some large images in it. I'm struggling to improve the very jerky scrolling performance. If I use ImageNamed to load the images, scrolling is jerky at first, but after the images are viewed, scrolling is smooth. I know ImageNamed adds the images into the system cache, so my question is: is it possible to pre-load the images into the system cache before they are viewed?
I've tried by adding the following code to my viewDidLoad method:
for (int i = 0; i < appDelegate.detailSectionsDelegateDict.count; i++) {
NSString *imageString = [NSString stringWithFormat:#"%#",[[appDelegate.detailSectionsDelegateDict objectAtIndex:i] objectForKey:#"MainTrackImage"]];
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
UIImage* theImage;
theImage = [UIImage imageNamed:imageString];
[imageCacheArray insertObject:theImage atIndex:i];
}
I then draw the correct image from the imageCacheArray in my CellForRowAtIndexPath method. But the result is still jerky scrolling.
Thanks!
Getting a table view with images (especially large ones) to scroll smoothly is not as trivial as you might think. Loading up a bunch of images with [UIImage imageNamed:] will very quickly cause springboard to kill your app as it starts to exceed memory capacity. Take a look at the Core Animation session videos from this year's WWDC, specifically look at session 425, "Core Animation in Practice, Part 2" They cover this exact topic and it's very well done. You can also get the relevant source code if you sign in with your developer account.
Related
Using ALAssetsLibrary the Thumbnail images takes time to load.is there any solution to load images faster.
the images are more than 900 images in photos.
Code:
[ALAssetsGroupObj enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger i, BOOL *load)
{
if(asset == nil)
{
asset;
}
UIImage* thumbImage = [UIImage imageWithCGImage:[asset thumbnail]
}
Thanks
No, there is no way to make it go any faster. But there are a few tricks:
Cache them to a static NSDictionary. Next time you'll need to draw them in your app, it will be much faster than pulling them from the library. I've tried with far more than 900 thumbnails.
Your screen won't fit 900 thumbs. As the user scrolls, you can populate the images in blocks and load say 16-32 or so per time. This is a bit tricky though as both the operation to draw the thumbnails and the scrolling needs to run on the main thread.
I am loading image with data in my table view. Images are coming from web. I created method to get image url in model class.Model class has Nsdictionary and objects.
But this images is slowing down scrolling .Here is my code
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",
[(Tweet *)[recentTweets objectAtIndex:indexPath.row]urlString]]]]];
cell.imageView.image = image;
Please tell Where I am going wrong?
Use lazy loading and do your own drawing. Try to understand the techniques on the sample projects I linked. These are the best ways to improve the performance of tables with images.
here is the methodology I use for loading images into a UITableView from a remote location:
in your .h file, declare a mutable dictionary for storing the images:
NSMutableDictionary *images;
initialize the dictionary in -init... or in -viewDidLoad
images = [[NSMutableDictionary alloc]init];
in the .m, tableView:cellForRowAtIndexPath:, see if the image exists in your dictionary for the indexPath
UIImage *img = [images objectForKey: indexPath];
if the image does exist, just set it.
if (img) cell.imageView.image = img;
if the image does NOT exist, set the cell's image to a temporary image...
if (!img) cell.imageView.image = [UIImage imageNamed:#"imageUnavailable.png"];
AND add that image to your dictionary so it doesnt try to refetch the image if you scroll off and back to that image before it loads...
[images setObject:[UIImage imageNamed:#"imageUnvailable.png"] forKey: indexPath];
then, in this same block, use an NSOperationQueue, and a custom NSOperation ( here is a reference - NSOperation and SetImage) to get your image and call back into your UITableViewController with that image.
in your callback, add your image to the dictionary (overwriting the temp image) and call [tableView reloadData]
this will give you a nice non blocking user experience.
The are a couple of ways how to do it. I had the best experience with a Queue for the HttpRequests, which I pause during the scrolling process.
I highly recommend this framework for that:
http://allseeing-i.com/ASIHTTPRequest/
I also implemented an image cache, which only loads the images if there weren't in the cache.
And the last tweak was to actually draw the images instead of using a high level uicomponent like the UIImageView
The number one reason your code is slow right now is because you're making a network call on the main thread. This blocks an UI from being updated and prevents the OS from handling events (such as taps).
As already suggested, I recommend ASIHTTPRequest. Combine asynchronous requests with an NSOperationQueue with a smaller concurrency count (say, 2) for the image requests, caching, and reloading the rows when images come in (on the main thread) (also only reloading the row if its currently visible). You can get a smooth scrolling table view.
I have an example of this on github here: https://github.com/subdigital/iphonedevcon-boston
It's in the Effective Network Programming project and includes a set of projects that progressively improve the experience.
Download the images before you load the tableView, and store them in an NSArray. Then when the cellForRowAtIndexPath method is called, show a loading image until the image is in the array.
I am loading images into a UIImage with the values of a slider (these are pages or slides, if you like). This UIImage switches them very fast with the use of a slider. Problem is, at some point, the app crashes on the device with an error of:
2011-04-02 17:39:01.836 Book1[2123:307] Received memory warning. Level=1
Here's the code:
- (IBAction)slidePages:(id)sender{
int sliderValue = pageSlider.value;
NSString *slideToPage = [NSString stringWithFormat:#"P%i.jpg", sliderValue];
imagePlaceholder.image = [UIImage imageNamed:slideToPage];
pageDisplay.text = [NSString stringWithFormat:#"Page %i", sliderValue];
currentPage = sliderValue;
}
Is there anything I could do to make it more efficient? Maybe the error is somewhere else but I'm guessing it has to do with the fast loading of images.
Still, I don't know how iOS deals with this. Every time I load a new image into the UIImage what happens with the "unloaded" one?
Thanks in advance.
One imortant aspect of [UIImage imageNamed] is that it caches all images loaded in that way and they never get unloaded, even if you dealloc the UIImage that was created! This is good in some circumstances (e.g. smallish images in UITableView cells), but bad in others (e.g. large images).
The solution is to use [UIImage imageWithData] which does not do this caching and which unloads the data when the UIImage is dealloc'd.
More info and discussion here:
Difference between [UIImage imageNamed...] and [UIImage imageWithData...]?
Update
This question has some good info on the question of [UIImage imageNamed:] not emptying its cache when a memory warning occurs.
[UIImage imageNamed:] caches images, so every image thus loaded effectively leaks. I don't know what the canonical solution is, but one option might be to load the image with CGImageCreateWithJPEGDataProvider() and initialise it with [UIImage imageWithCGImage:].
The original (wrong) answer:
You may need to release the previous image before loading the current one.
Loading a bunch of large images in iOS has always been a memory issue. As Marcelo mentioned, you should only keep around images are you currently viewing. All other images should be released so they can be garbage collected and the memory freed up. In my experience, even loading 2 fairly large images (500k-2mb each) will cause memory issues, especially on older devices with less RAM.
I have been having a tough time with memory consumption in a hefty app. I've got rid of almost all memory leaks. One section has a zoomable UIScrollView of a map that's pretty large: 2437x1536. It chooses between pngs in an array. Before I was using +imageNamed:, but I heard that this can make apps sometimes run poorly because it may keep the image in cache, which can consume more memory even if you're out of the view that was using it. Now I'm using +imageWithData:. The app hasn't crashed yet, but upon the 4th or 5th time of launching the map section, only some of the image appears, and there's flickering black areas. It didn't happen before with imageNamed. Sometimes it entirely disappears except for just a rectangular upper corner, and I go back to another view, and an image is flashing there too.
Here's what I have to display the map image. It's in a view's -initWithFrame: method:
mapList = [[NSMutableArray alloc] init];
[mapList addObject:#"Pacific_Map"];
[mapList addObject:#"Atlantic_Map"];
NSString *mapFileLocation = [[NSBundle mainBundle] pathForResource:[map_List objectAtIndex:mapNum] ofType:#"png"];
NSData *mapIMGData = [NSData dataWithContentsOfFile:mapFileLocation];
mapImageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:mapIMGData]];
Anything obvious that would be causing this effect?
Sorry I was allocating an image for the external screen twice. This still eventually makes it crash. I thought I'd be not caching the image with this technique.
So this one is pretty odd ad I'm not sure if the trouble is with the AssetsLibrary API, but I can't figure out what else might be happening.
I am loading an array with ALAssets using the -enumerateAssetsUsingBlock method on ALAssetsGroup. When it completes, I am loading a custom image scroller. As the scroller finishes scrolling, I use NSInvocationOperations to load the images for the currently visible views (pages) from the photo library on disk. Once the image is loaded and is cached, it notifies the delegate which then grabs the image from the cache and displays it in an image view in the scroller.
Everything works fine, but the time it takes from when -setImage: actually gets called to the time it actually shows up visibly on the screen is unbearable--sometimes 10 seconds or more to actually show up.
I have tried it both with and without image resizing which adds almost nothing to the processing time when I do the resizing. As I said, the slowdown is somewhere after I call -setImage on the image view. Is anyone aware of some sort of aspect of the AssetLibrary API that might cause this?
Here's some relevant code:
- (void)setImagesForVisiblePages;
{
for (MomentImageView *page in visiblePages)
{
int index = [page index];
ALAsset *asset = [photos objectAtIndex:index];
UIImage *image = [assetImagesDictionary objectForKey:[self idForAsset:asset]];
// If the image has already been cached, load it into the
// image view. Otherwise, request the image be loaded from disk.
if (image)
{
[[page imageView] setImage:image];
}
else {
[self requestLoadImageForAsset:asset];
[[page imageView] setImage:nil];
}
}
}
This will probably mess up any web searches looking to solve problems with the AssetsLibrary, so for that I apologize. It turns out that the problem wasn't the AssetsLibrary at all, but rather my use of multi-threading. Once the image finished loading, I was posting a notification using the default NSNotificationCenter. It was posting it on the background thread which was then updating (or trying to update, at least) the UIImageView with -setImage. Once I changed it to use -performSelectorOnMainThread and had that selector set the image instead, all was well.
Seems no matter how familiar I get with multi-threading, I still forget the little gotchas from time to time.