setImage for UIImageView in uitableviewcell lags scrolling in iOS - iphone

I have a gallery view of photos that are downloaded from the internet so I used Enormego's EGOImageView. I noticed that when i scrolled down my tableview after the images were in the cache, the scrolling would lag. I immediately found that when the image was retrieved from the hard drive with return [UIImage imageWithContentsOfFile:cachePathForKey(key)]; it was working on the main thread so I added the operation to an NSOperationQueue. This reduced the lag by half but the scrolling still stuttered. After going through the code, I noticed that in the success method
- (void)imageLoaderDidLoad:(NSNotification*)notification {
if(![[[notification userInfo] objectForKey:#"imageURL"] isEqual:self.imageURL]) return;
UIImage* anImage = [[notification userInfo] objectForKey:#"image"];
self.image = anImage;
[self setNeedsDisplay];
if([self.delegate respondsToSelector:#selector(imageViewLoadedImage:)]) {
[self.delegate imageViewLoadedImage:self];
}
}
commenting out the self.image = anImage; got rid of the lag completely (but obviously I get no image). And as far as I can tell, if I want to alter the UI, it must be done in the main thread. Is there a way to set the image for the EGOImageView without it lagging the scrolling?
Note: the JPGs are around 50kB
Thanks

Subclass uitableViewCell and do your own drowing in drawContentView.
Resize images in background thread and then present them in cells.
P.S: if the code found on git hub is not good enough for you, try to write your own that suits your problem.

From what I understand, all the steps required to initiate an asynchronous disk query takes a couple milliseconds on the mainthread, and that is enough time for the scrolling to look like it stutters, so I decided to completely remove hard drive caching for large images and instead create an NSMutableDictionary which holds the UIImage as an object, and the NSURL.absoluteString as the key. This works seamlessly but obviously has the disadvantage of being a memory hog. I checked out the memory usage of some photo-sharing apps and I've been able to get the memory usage for the app to over 100MB so it seems everybody else is doing this.

Related

Single ANIMATED UIImageView background throughout app

I've been reading everything I can find on here about this topic but am still not sure the best way to proceed. I have a heavy UIImageView that uses an array of fat UIImages acting as an animated loop. This UIImageView is serving as the background for every screen in the app. (Client's request, not mine, so don't hate.) We've optimized the png files as small as they can go but it's still a pretty heavy load.
I've read several posts about how UIImage searches the cache for an existing image of that name (Ex. Shared UIImageView for background image throughout app - Singleton Property) but this doesn't seem to be happening. I've also been reading here about singletons and instantiating in the appdelegate but I'm not sure if either of these are the right way to proceed.
What's the best way to load the UIImageView once over the life of the app and use it in the background of every viewcontroller? Btw, because it takes several seconds to load, I'm going to be adding a "loading" page at app start that uses a single static image and an activity indicator.
Also, I'm not as familiar with testing and performance tools. Which one should I be using to test performance and make sure this is what is causing the hesitations throughout the app?
Apologies in advance for the noob questions - I generally avoid asking questions at all but sometimes, as in this case, I don't even know where to begin the research.
I'm rather pleased with myself. I went with the AppDelegate technique and it's working beautifully. I declared a UIImageView property in the AppDelegate, then add the image code to its getter:
- (UIImageView *)backgroundImageView {
if (!_backgroundImageView) {
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:150];
for (int imageNum = 0; imageNum < 150; imageNum++) {
[tempArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"image_00%03i.png",imageNum]]];
}
_backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage animatedImageWithImages:tempArray duration:4.0]];
}
return _backgroundImageView;
}
This also allowed me to kill the object in applicationDidReceiveMemoryWarning.
Then I created a helper class that I can pop into all of my UIViewController classes:
+ (void)placeBackgroundImageUnderView:(UIView *)masterView {
myAppDelegate *appDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
//Correct for shrinkage
appDelegate.backgroundImageView.frame = masterView.frame;
[masterView addSubview:appDelegate.backgroundImageView];
[masterView sendSubviewToBack:appDelegate.backgroundImageView];
}
This allowed me to add a single line in each viewDidLoad method:
[HelperClass placeBackgroundImageUnderView:self.view];
The thing is, just using UIImage alone should cache the whole thing and speed up load time. But it seems that every now and then the images would have to reload - memory issues? So this allows me more control over it by instantiating the object once and using that same object, while also being to set that object to nil to free up memory if needed.
Since adding it, the whole app has been loading much faster. I added an additional load screen with an activity indicator for that initial load (also works beautifully) and everything after that is instant happy.

IOS / iPhone loading images into UIImage based on slider value memory issue

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.

ScrollView with large images scrolls too slowly

When I am scrolling images frequently in a UIScrollView then after some images, the next image takes time to load... it's not taking too much time but looks odd.
Suppose I have 27 images in a scrollView. When I start to scroll these images, for 1 or 2 images it scrolls smoothly but when I scroll again to see the 3rd image it takes time to load. Then when I start the images scrolling again from the 3rd image, it behaves like before.
I can't load all 27 images at a time or my app crashes.
When I slowly scroll the scrollview then I don't have this problem.
My code is below:
//Taking image view for 27 images;
int x=0;
for(int i = 1; i<=27; i++) {
imageView = [[UIImageView alloc] init];
imageView .frame = CGRectMake(x,0,768,1024);
imageView.tag=i;
imageView.image=nil;
imageView.userInteractionEnabled=YES;
[contentView addSubview:imageView];
x+=768;
}
//setContentOffset of the scrollView -->ContentView
[contentView setContentOffset: CGPointMake((imageNumber-1)*768, 0) animated: YES];
//desire image which i want to see from the start of the scrollview
pageNumber=imageNumber;
int pageBefore=pageNumber-1;
int pageAfter=pageNumber+1;
//Views for image
for( UIImageView * views in [contentView subviews]){
if(views.tag==pageNumber){
if(views.image==nil){
NSLog(#"entering");
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageNumber]];
[views.image release];
}
}
if(views.tag==pageBefore){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageBefore]];
[views.image release];
}
}
if(views.tag==pageAfter){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageAfter]];
[views.image release];
}
}
My alarm bells rang when I saw this;
imageView .frame = CGRectMake(x,0,768,1024);
Apart from the space before .frame, are you saying that your images are 768x1024? That's HUGE and I suspect your problems are memory ones rather than code ones.
Be aware that in particular, using UIImage imageNamed: is likely to cause grief with such large images as that method caches the images in memory. You may wish to consider using alternative methods that load the image from a file each time.
You should try use the EGOImageView, it has caching build in which might help with your performance issues. You can implement a placeholder image to show the user that an image is being prepared for viewing. The image will load in another thread before being displayed, giving you smoother scrolling performance. The EGOImageView is part of the EGOImageLoading library.
https://github.com/tastefulworks/EGOImageLoading
As an alternative you could create your own lazy loading mechanism to increase scrolling performance. E.g. once a user stops scrolling for a second, start loading the image, otherwise display placeholder image if not yet the correct image is cached.
Edit: when thinking more about this issue, I realize caching won't help much (since you already load image from disk), but the asynchronous loading of images should help with the scroll performance, so make use of NSThread or NSOperation to load the image in a background thread, then notify the main thread that the image is loaded and ready for display.

UIImage, releasing the decompression cache?

I have an array of UIImages that contains some .jpg images downloaded from the net when the app starts. I also have a table view that shows some of these images in its cells. The problem is that when I scroll the table, the app's memory consumption always increases up to the point where the app crashes due to low memory conditions. The table cells seem to be reused fine, so my theory is the following.
Since the UIImageView in a table cell only retains one of the elements in the image array, when the cell gets reused, and a new image is assigned to it, the previously used image is of course not destructed (the cell releases it, but the array still retains). However, the decompression cache used to hold the raw image data (computed the first time the UIImage is displayed by a view) belongs to the UIImage itself, so it also remains. But I'm just guessing about all this.
Can this really be the problem? If so, how can I work around it? My first idea was to create a copy of the UIImage whenever it is assigned to a cell, but looks like UIImages can't be deep copied. Is there any way to tell a UIImage to keep the compressed jpg data in memory but throw away the decompression cache? (In the worst case I guess I can still download all the images and store them in local files, then load them from there and completely release the UIImages when not displayed anymore, but this doesn't seem to be an elegant solution.)
--
I can't post the original code (as suggested in comments) as it is fairly complicated with custom table cells and custom views and even a background thread downloading the images, but I've just created a small test app and it seems to show the same behavior. Here's a little function that is called when the user taps a UIButton:
- (IBAction)onNext:(UIButton*)sender
{
static NSMutableArray* images = nil;
if (!images)
{
NSArray* names = [NSArray arrayWithObjects:
#"IMG_2957.JPG",
#"IMG_2962.JPG",
#"IMG_2965.JPG",
#"IMG_2970.JPG",
#"IMG_2971.JPG",
#"IMG_2978.JPG",
nil];
images = [NSMutableArray new];
for (int i = 0; i < names.count; ++i)
[images addObject:[UIImage imageNamed:[names objectAtIndex:i]]];
int i = 42;
}
static int current = 0;
imageView.image = [images objectAtIndex:current];
++current;
}
Yes, I know that the images array is leaking, that's not the point (in the original code I also want to retain the images for the entire lifetime of the app, and only release on quitting). But. According to Memory Monitor, after the first tap (all images get loaded, and the first one is displayed), the app consumes ~5MB (all jpgs loaded, one of them decompressed). After all subsequent taps, the memory consumption increases by ~2MBs (yep, I tested with 1024x768 images). So it looks like decompressed data of the previously displayed images is still there. I know this wouldn't be a problem if I released the image once it isn't visible anymore, and that would also release the decompressed buffer, but if possible, I'd like to retain all the images, and only release the decompressed data. Is that possible?
If you're reusing cells, make sure those cells implement:
-(void)prepareForReuse
{
// releasing the image
self.imageView = nil; // Or release depending on you memory strategy
self.labelView = nil; // get them all
}
I don't think UIImage has no memory leaks that fatal. I have been working on few applications which has to deallocate UIImage a lot due to memory constraint, but it's still working fine. I believe your image leaks somewhere. Show us some code so someone will point that out for you.

AssetsLibrary and ImageView -setImage Slowness

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.