ScrollView too slow – Memory management issue? - ios

In my app, I use a scroll view in which I display 300 image (400 kB).
It takes nearly 2 minutes, and as a result, when I scroll up and down, it isn't smooth.
How can I solve this problem?
Note: I have noticed that after I scroll down to end of scrollview once, it gets smooth.
The issue with scrolling only appears the first time.

Try adding this in your load function (where you load your images). If you are using a while or for loop to load images, add this code to the loop.
NSURL *url=//here url for your image
dispatch_queue_t currentQueue = dispatch_get_current_queue();
dispatch_queue_t dataConvertorQueue = dispatch_queue_create("Fetching images", NULL);
dispatch_async(dataConvertorQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(currentQueue, ^{
//replace imageView with your imageView outlet
imageView.image = [[UIImage alloc] initWithData:data]];
});
});
dispatch_release(dataConvertorQueue);
This code helps you fetch and show images from a url and it works great with a tableview. It might not be exactly what you want but it'll give you an idea on how to do it. Hope it'll help you.

Do not load all images upfront. Just load the images to show at the moment and the next + last picture.
As far as i understand you load all the images when the scrollview is loaded/displayed. Since you have a lot of images it will be much faster and much more memory saving if you just load/have in memory three images.
This will be the previous/left shown image and not visible - the current shown and visible image and the next/right image which is not visible.
So if you move/swipe to the left you basicly move the scrollview to the left and show the right image which is already loaded. In the background you will load the next right image while you will deallocate the left most image.
At all times you just have maximal 4 images loaded.
Take a look at the infinite scrollview example of WWDC 2011 - Session 104 advanced scroll view for a good visual example and sample code.

I think this will solve your problem. This question may give you some context:
How to reuse/recycle custom element like uitableviewcell does?

Related

Why does my selected image only display in the UIImageView the first time I load the view and never again if I select it again

This is my first question on StackOverFlow so I hope I'm doing all good for now.
To explain my problem as best as possible, here's the 2 concerned views of my MainStoryBoard : http://i.imgur.com/aRZbpp6.jpg?1
On the first one, a list of places is displayed with multiple infos hidden like GPS coordonates, phone number, description of the place, name of the picture (xxx.jpg/.png).
When the user click on one of the places in the list, the app brings him to the next view where all of these infos are displayed.
-> Everything appears correctly.
But if I go back to the list, and click again on the same place, then the picture won't display anymore, the UIImageView stays empty.
And it's happening with every places. The picture is displayed only once.
As all pictures are stored in the same place, I "construct" the URL I will set for NSURL.
NSString *DebutLien = #"http://www.otmval.com/membre/img_activites/miniature/";
self.currentPlace.photo1 = [NSString stringWithFormat:#"%#%#",DebutLien,self.currentPlace.photo1];
// Loading the picture in background
dispatch_async(dispatch_get_global_queue(0,0),
^{
NSURL * imageURL1 = [NSURL URLWithString:self.currentPlace.photo1];
NSData * imageData1 = [NSData dataWithContentsOfURL:imageURL1];
UIImage * image1 = [UIImage imageWithData:imageData1];
dispatch_async(dispatch_get_main_queue(),
^{
[imageView setImage:(image1)];
});
});
imageView.contentMode = UIViewContentModeScaleAspectFit;
The result is here : http://i.imgur.com/c6tIhbL.jpg?1
As you can see on the left, everything is well displayed.
But on the second image, if I re-select the same place from the list, then the picture do not appear and I don't know why yet.
Good thing to know, the problem is happening even without using dispatch queue.
I've been searching for a long time but it seems my problem isn't really easy to describe, so I hope I will find answers here.
Thanks in advance for your comments and taking time to help me with this ! :)
your problem maybe in calling the async block. You can try sync on the inner dyspatch block.
Here you can find detailed explanation why:
dispatch_sync inside a dispatch_async
I don't have enough reputation, so i will post it as an answer.

Vast memory required with UICollectionView images set from NSDictionary

I'm making an app which has lots of entries from a database displayed in a UICollectionView, and each item in the collectionview displays an image. However, there are lots of duplicate images (ie, my UICollectionView has 200 items but there's only about 30 possible images).
Initially I assigned these images by calling cell.imageview.image =[UIImage imageWithContentsOfFile] in my cellForItemAtIndexPath method, but that caused a bit of scrolling lag, presumably due to having to read the image from the internal memory each time.
Now I want to do all the I/O in viewDidLoad by creating an array/dictionary and just referencing that each time cellForItem is called. When I do this and create a dictionary of UIImages in viewDidLoad and have cell.imageview.image = [imageDictionary objectForKey:] it displays images correctly and scrolls really smoothly but the memory used is huge (it just increases till it's scrolled all the way to the bottom so I presume it has all 200 image instances in the RAM).
However, if alternatively I fill the dictionary in viewDidLoad with NSData of the image and set the image using cell.imageView = [UIImage imageWithData:[dataDictionary objectForKey:]], the memory used is far more efficient and reflects only what is currently displayed on the screen. However, the scrolling becomes slightly laggy again.
My question is: why does the memory leak only occur when I fill the dictionary with UIImages rather than NSData? Does iOS automatically cache images unexpectedly or something?
Hope that made sense - it's hard to be clear without going too much into the detail of my code.
Use your first approach to set the image as you scroll. However, load the images in a background thread and then set the image on the main thread so you don't inhibit scrolling. Also, make sure your images are the same size as the image view (ie. not huge) so they don't take up a ton of memory.
Example:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
UIImage *image = [UIImage imageWithContentsOfFile... // do stuff
dispatch_async(dispatch_get_main_queue(), ^(void) {
[[cell imageView] setImage:image];
});
});

iOS slow to release images while scrolling (no tableview)

Here is the problem :
I am writing an app which displays some pictures, with a treemap layout (for an example, see https://raw.github.com/beckchr/ithaka-treemap/master/Core-API.png)
This layout is displayed in a UIScrollView. Since many pictures can be added to that scrollview, I want to release the ones which are not on currently on screen. I am not using ARC.
At my point, I know which pictures I should release, and how to do it while scrolling (calling some "unload" method). There is no useless call of that method. The problem is that, when pictures are released, the scrolling stops for a little moment (a few ms, but this is enough to be bad looking, making the scroll kind of "jumping" and slow, not smooth at all).
What I've tried (put in the body of my "unload" method) :
imageview.image = nil
performSelectorInBackground:#selector(effectiveUnload) withObject:nil
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0,^(void){
dispatch_sync(dispatch_get_main_queue(), ^(void){
imageview.image=nil}
}
I think this problem is weird, since there is absolutly no slowing effect with memory allocation, but only with memory release.
Thanks for help, don't hesitate to ask for more information.
did you try removeFromSuperview to remove the imageview from scrollview
Don't add more and more UIImageViews - recycle them!
To save as much memory as possible you should follow the UITableView way of recycling views:
1. Once a view has left the visible area, add it to a "views pool" (and remove it from its superview. It's not an expansive operation!)
2. When a new view becomes visible, first check if there's a view in the pool. If not, then create a new view.
I know my answer doesn't answer your question directly, but that's the way to go. Otherwise you'll run out of memory eventually.

Load local UIImage asynchronously with caching

UIScrollViewController
|-----UIImageView1, 2, 3, 4, 5...
I have a UIScrollViewController which has several subviews. Every subview contains a UIImageview, when scrolling the UIImageView, pages on either side of current page will be loaded using
imageView = [[UIImageView alloc] initWithContentsOfFile:fileName];
But when the local image file becomes really big (>2MB), the scrolling becomes very influent amd gets stuck.
Is there a way to avoid this? For example using a cache or doing it asynchronously?
You can load images asynchronously, in another thread, or if you support only iOS higher than 4, it's quite easy to perform it with blocks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
UIImageView* imageView = [[[UIImageView alloc] initWithContentsOfFile:fileName]autorelease];
dispatch_async(dispatch_get_main_queue(), ^{
//Maybe correct the frame too
[self.view addSubview:imageView];
});
});
If you can't use blocks, you can execute in different thread, but it's a bit more involved.
Anyway, I recommend:
Better have created UIImageViews added to the scrollview, with a 'dummy' or 'spinner', then when user interaction stops (detected via delegate method), you can reload the visible imageviews.
When user is scrolling, better to load and cache UIImage objects for the images you want, (store them in an array or dictionary for example).
Scale the UIImage content to fit the UIImageView frame inside ScrollView. That way during rendering time, UIImage will not 'autoscale' the provided image, so it will be faster.

Why can't I update the contents of my UIScrollView using NSOperationQueue / NSOperationInvocation?

I think I remember something about UI being updated on the main thread, could be the explanation, however my issue is that I have a UIScrollView with 1000+ images in it, and I wrote a routine that quickly will toggle the images visible in the view to show their real image, and when out of visible view, they switch to a placeholder image. All the offscreen images share the same placeholder, greatly reducing memory load.
I wrote two methods that toggle the next few tiles of the view on/off, and are kicked off if you have scrolled enough in a certain direction. The scrolling is detected via the UIScrollViewDelegate callback method scrollViewDidScroll.
This strategy works fine, but the scrollview scrolling is a little jittery on slow devices (iPad 1). I wanted to increase performance, so I thought I could change the code so that the two methods that update the scrollview contents were NSOperationInvocation, added to an NSOperationQueue.
However, when I do this, even with priority set to VeryHigh, the routines to not update the UI even though they seem to be called, and the queue does grow, when scrolling. The observed behavior is that the images retain their placeholder images and do not switch on.
Why doesn't this work?
Code for the NSOperationInvocation:
ImageThumbView *thumb = [boardThumbsArray objectAtIndex:lowestLoadedThumbTag - i];
[loadedThumbsArray addObject:thumb];
[thumb showThumbImage];
ImageThumbView is a UIButton Subclass
- (void)showThumbImage {
if (thumbImagePath != nil) {
thumbImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL fileURLWithPath:thumbImagePath]]];
} else {
NSLog(#"%#", #"Cannot show thumb image: thumbImagePath is nil");
}
[self setBackgroundImage:thumbImage forState:UIControlStateNormal];
[self setNeedsDisplay];
}
Look at Apple's "PhotoScroller" sample code. You should not be having 1000 views as subviews of scrollview. Recycle the ones that are no longer visible.
Also look at WWDC 2010 "Designing apps with scrollview" video. That explains the photoscroller sample code
Thanks

Resources