How Do I speed Up Image Loading Using The Assets Library? - iphone

I am writing an app that is a clone of the UIImagePicker but uses the Assets library. When the user selects a photo, it takes a little bit too long for the image to load. I notice that when I use the photos app which has identical functionality as to what I'm developing, that the image loading is a bit faster. I've heard another responder on this site mention the following in order to mimic the functionality of the photos app:
"Load the thumbnail image first (best with dispatch_async) - that should be really quick. When this has completed, load the fullscreen image like you did above. This is what apple does in the Photo App to provide a smooth user experience."
Does anyone have any code samples of how this can be accomplished? I'm not quite sure that I understand what he means.
Also here is my code for which I'm using to load an image (I'm passing the image as a parameter to another view controller):
myImage = [UIImage imageWithCGImage:[[myAsset defaultRepresentation] fullScreenImage]];

The class ALAsset has two methods to obtain thumbnails:
- (CGImageRef)thumbnail
- (CGImageRef)aspectRatioThumbnail
I bet they are faster than obtaining the full screen sized version of the asset.
Also, you can wrap them with an async operation. Be sure to update the UI in main thread. Roughly like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* obtain the image here */
dispatch_async(dispatch_get_main_queue(), ^{
/* update screen here */
});
[pool drain];
});
If you need to obtain thumbnails for videos you should use AVAssetImageGenerator. It has a method to obtain them asynchronously.
Look for Apple sample code (AVEditDemo and probably others working with assets library).

Related

Asynchronous images - Meant for displaying images from web only?

I have images in my documents folder which I am displaying on one of my screens. It takes times to load the images and display them on the screen similar to when loading images from web. As far as I know asynchronous imageView works for the later case. I might be wrong.
Is there anyway we can display images from documents folder asynchronously?
Take a look at SDWebImage. It is a UIImageView subclass that lets you display image asynchronously from a URL and with a useful cache. It is designed to work with Internet URLs, but I think it will also go with internal URLs.
Put the loading of images in the background thread as following
-(void)backgroundLoadImageFromPath:(NSString*)path {
UIImage *newImage = [UIImage imageWithContentsOfFile:path];
[myImageView performSelectorOnMainThread:#selector(setImage:) withObject:newImage waitUntilDone:YES];
}
Then call that thread wherever you need to set the image
[self performSelectorInBackground:#selector(backgroundLoadImageFromPath:) withObject:path];
Note, in backgroundLoadImageFromPath you need to wait until the setImage: selector finishes, otherwise the background thread's autorelease pool may deallocate the image before the setImage: method can retain it.

How to update ui in background without losing ui responsiveness? [duplicate]

I am creating a application which displays 8 thumbnails per page and it can have n pages. Each of these thumbnails are UIViews and are added to UIScrollView. However i have implemented Paging using the Apple sample code.
The prob:
Each thumbnail(UIView) takes 150
millisecs to be created and added to
scroll view
Hence for 3 pages it takes awful
huge time to be created and added to
the UI Scrollview.
At this point the scroll view is not very respsonsive and it is very jerky and gives a bad user experience
How can i create the thumbnails and add them to UIScrollview without affecting the touch responsiveness? I want them to run independent of the main thread which is resposible for handling touch events (i suppose).
Also i would like to mention that when a thumbnail is created i trigger a Async download of the image and the delegate method is called when download is complete.
Let me know the the options i have to make this more responsive and update UI without affecting the touch operations. The page control works fine with lazy loading of the grid of thumbnails.
TIA,
Praveen S
Grand Central Dispatch is easy to use for background loading. But GCD is only for after iOS4. If you have to support iOS3, performSelectorInBackground/performSelectorOnMainThread or NSOperationQueue are helpful.
And, be careful almost UIKit classes are not thread-safe except drawing to a graphics context. For example, UIScrollView is not thread-safe, UIImage imageNamed: is not thread-safe, but UIImage imageWithContentsOfFile: is thread-safe.
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
dispatch_apply([thumbnails count], concurrentQueue, ^(size_t index) {
Thumbnail *thumbnail = [thumbnails objectAtIndex:index];
thumbnail.image = [UIImage imageWithContentsOfFile:thumbnail.url];
dispatch_sync(mainQueue, ^{
/* update UIScrollView using thumbnail. It is safe because this block is on main thread. */
});
}
/* dispatch_apply waits until all blocks are done */
dispatch_async(mainQueue, ^{
/* do for all done. */
});
}
I was having a similar problem.
What i did was at an instance i kept only 3 pages in the memory and cleared remaining all.
If suppose there are 3 screens s1, s2, s3. And the user is viewing s2. Whenever he scrolls to s3 i will remove s1 and load a new page s4.
So that the users will have a better experience. And less memory will be occupied.
Whether you are using a subview or a separate ViewController for each "page" or element of the Scrollview, the jerkiness or poor performance can be helped by changing the location of your code.
Specifically the apple sample code for a scrollview with pagecontrol has something like this:
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
However, that code appears in their sample in the method "scrollViewDidScroll". It's trying to do multiple heavy lifting by both scrolling and loading at the same time. Even if your images are local this is nasty.
If you move this and related code including a reference to the current page to "scrollViewDidEndDecelerating" the jerkiness of the interface is resolved because the loading happens while the scrollview is no longer moving.

How to make ui responsive all the time and do background updating?

I am creating a application which displays 8 thumbnails per page and it can have n pages. Each of these thumbnails are UIViews and are added to UIScrollView. However i have implemented Paging using the Apple sample code.
The prob:
Each thumbnail(UIView) takes 150
millisecs to be created and added to
scroll view
Hence for 3 pages it takes awful
huge time to be created and added to
the UI Scrollview.
At this point the scroll view is not very respsonsive and it is very jerky and gives a bad user experience
How can i create the thumbnails and add them to UIScrollview without affecting the touch responsiveness? I want them to run independent of the main thread which is resposible for handling touch events (i suppose).
Also i would like to mention that when a thumbnail is created i trigger a Async download of the image and the delegate method is called when download is complete.
Let me know the the options i have to make this more responsive and update UI without affecting the touch operations. The page control works fine with lazy loading of the grid of thumbnails.
TIA,
Praveen S
Grand Central Dispatch is easy to use for background loading. But GCD is only for after iOS4. If you have to support iOS3, performSelectorInBackground/performSelectorOnMainThread or NSOperationQueue are helpful.
And, be careful almost UIKit classes are not thread-safe except drawing to a graphics context. For example, UIScrollView is not thread-safe, UIImage imageNamed: is not thread-safe, but UIImage imageWithContentsOfFile: is thread-safe.
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
dispatch_apply([thumbnails count], concurrentQueue, ^(size_t index) {
Thumbnail *thumbnail = [thumbnails objectAtIndex:index];
thumbnail.image = [UIImage imageWithContentsOfFile:thumbnail.url];
dispatch_sync(mainQueue, ^{
/* update UIScrollView using thumbnail. It is safe because this block is on main thread. */
});
}
/* dispatch_apply waits until all blocks are done */
dispatch_async(mainQueue, ^{
/* do for all done. */
});
}
I was having a similar problem.
What i did was at an instance i kept only 3 pages in the memory and cleared remaining all.
If suppose there are 3 screens s1, s2, s3. And the user is viewing s2. Whenever he scrolls to s3 i will remove s1 and load a new page s4.
So that the users will have a better experience. And less memory will be occupied.
Whether you are using a subview or a separate ViewController for each "page" or element of the Scrollview, the jerkiness or poor performance can be helped by changing the location of your code.
Specifically the apple sample code for a scrollview with pagecontrol has something like this:
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
However, that code appears in their sample in the method "scrollViewDidScroll". It's trying to do multiple heavy lifting by both scrolling and loading at the same time. Even if your images are local this is nasty.
If you move this and related code including a reference to the current page to "scrollViewDidEndDecelerating" the jerkiness of the interface is resolved because the loading happens while the scrollview is no longer moving.

Question about Apple's LazyTableImages sample - Doesn't behave exactly like the app store

I have a UITableView with a list of items, each having it's own image. I thought Apple's LazyTableImages sample project would be perfect to learn from, and use to implement the same kind of process of downloading images asynchronously, after the original list data is retrieved.
For the most part, it works quite well, except I did notice a subtle difference in behavior, between this sample app, and how the actual app store downloads images.
If you launch the LazyTableImages sample, then do a quick flick-scroll down, you'll see that the images do not get displayed until after the scrolling comes to a complete stop.
Now, if you do the same test with a list of items in the actual app store, you'll see that the images start displaying as soon as the new items come into view, even if scrolling hasn't stopped yet.
I'm trying to achieve these same results, but so far I'm not making any progress. Does anyone have any ideas on how to do this?
Thanks!
I'm baffled that nobody could answer this...
So, I eventually figured out how to acheive the exact same effect that is used in the actual app store, in regards to how the icons are downloaded/displayed.
Take the LazyTableImages sample project and make a few simpled modifications.
Go into the root view controller and remove all checks regarding is table scrolling and/or decelerating in cellForRowAtIndexPath
Remove all calls to loadImagesForOnScreenRows, and thus remove that method as well.
Go into IconDownload.m and change the startDownload method to not do an async image downlaod, but instead do a sync download on a background thread. Remove all the code in startDownload, and add the following, so it looks like this:
- (void)startDownload
{
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(loadImage) object:nil];
[queue addOperation:operation];
[operation release];
[queue release];
}
Then, add a loadImage, like this:
- (void)loadImage
{
NSData *imageData = [[NSData alloc] initWithContents OfURL:[NSURL URLWithString:appRecord.imageURLString]];
self.apprecord.appIcon = [UIImage imageWithData:imageData];
[imageData release];
[self performSelectorOnMainThread:#selector(notifyMainThread) withObject:nil waitUntilDone:NO];
}
Then, add notifyMainThread like this:
- (void)notifyMainThread
{
[delegate appImageDidLoad:self.indexPathInTableView];
}
Done! Run it, and you will see the exact app store behavior, no more waiting to request image downloads until scrolling stops, and no more waiting for images to display until scrolling stops, or until user has removed their finger from the screen.
Images are downloaded as soon as the cell is ready to be displayed, and the image is displayed as soon as it is downloaded, period.
Sorry for any typos, I didn't paste this from my app, I typed it in, since I'm away from my mac right now...
Anyway, I hope this helps you all...
Check out UIScrollViewDelegate. I've implemented something like this by listening for scrollViewDidScroll:, calculating the scroll speed (by checking the contentOffset against the last recorded contentOffset, divided by the difference in time), and starting to load images once the speed drops below a certain threshold. (You could achieve something similar with UIScrollViewDelegate's scrollViewDidEndDragging:willDecelerate: as well).
Of course, you don't have to check the speed; you could just load images on UITableViewDelegate's tableView:willDisplayCell:forRowAtIndexPath: whenever you see a new cell, but I've found that if the user is flipping through tons of cells, you don't need to bother until you see that they're going to slow down to browse.

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.