I need to load some images into a UIScrollView that is paging. I am using ASIHTTP so loading from the internet is already asynchronous, however I'm trying to load images from disk if i have them cached there but I don't know how to load those asynchronously (It lags between pages)
Is there any easy method that I can use without having to implement full multithreading?
Can anyone link me some good documents on how to do something like this?
Thanks in Advance.
Use...
[self performSelectorInBackground:#selector(doStuff) withObject:nil];
You can use this:
[self performSelectorInBackground:#selector(someMethod) withObject:nil];
There are other methods too for multithreading. Here's Apple's Guide on Multithreading.
Related
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.
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.
Currently I am retrieving a bunch of images from the internet and scaling them and then displaying them on my view.
The problem is I am doing it in the viewDidLoad method - so when the user taps they have to actually wait for this processing to happen and then the view is shown which causes a slight delay.
Is there anyway I could show the view and then somehow spark off the loading of the images AFTER the user has the view in front of them - similar to how a web page loads?
- (void)configureImages
{
if ([currentHotel.hotelImages count] > 0)
{
imageView1.image = [self getScaledImageFromURL: [currentHotel.hotelImages objectAtIndex:0]];
imageView2.image = [self getScaledImageFromURL: [currentHotel.hotelImages objectAtIndex:1]];
imageView3.image = [self getScaledImageFromURL: [currentHotel.hotelImages objectAtIndex:2]];
}
}
Consider NSOperation/NSOperationQueue, discussed in the Concurrency Programming Guide. There are several links to examples here.
Apps should use the asynchronous networking APIs for all networking to avoid blocking the user experience. It's best to avoid adding threads (such as happens when you use NSOperationQueue) for tasks like networking where the OS already provides async alternatives.
Apple supplies the async NSURLConnection, which works well but is a bit tedious to use. You can add a simple wrapper like gtm-http-fetcher which reduces async fetches to a single line with a callback when the load has finished. That will let you start all the loads in your viewDidLoad: method without stalling the user interface.
I'm working on an app right now that was working fine until I started implementing some threading for background loading of images. Now theres no crashes but the keyboard does not display. That is to say its invisible (I can still type, I just cant see the actual keyboard).
The only thing I've done was implement threading so I'm wondering if I'm somehow messing with the thread the keyboard runs on or something?
The threads I'm calling are like:
[NSThread detachNewThreadSelector:#selector(loadWebView:)
toTarget:self withObject:[NSNumber numberWithInt:pageNum]];
and
[scrollView performSelectorOnMainThread:#selector(addSubview:)
withObject:curWebView waitUntilDone:NO];
Thanks in advance.
UIWebViews (and all UI elements) cannot be used on background threads safely. If you wish to load images in the background, use NSURLConnection's asynchronous loading methods.
I've been looking for some concrete scenarios for when NSOperation on the iPhone is an ideal tool to use in an application. To my understanding, this is a wrapper around writing your own threaded code. I haven't seen any Apple demo apps using it, and I'm wondering if I'm missing out on a great tool instead of using NSThread.
The ideal solution here would be to describe a use-case scenario for NSOperation and how you would use it to solve your problem(s).
Cocoa Is My Girlfriend has a good tutorial on the use of NSOperation and NSOperationQueue. The tutorial makes use of NSOperation to download several webpages simultaneously in separate threads.
Also, see this article from Mac Research.
The way I use it in my iPhone apps is to basically create an NSOperationQueue member in my application delegate and make it available through a property. Then every time I need to run something in the background, e.g. download some XML I'll just create an NSInvocationOperation and send it to the queque.
NSInvocationOperation *operationToPerform = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updateXML) object:nil];
[[(MyAppDelegate *)[[UIApplication sharedApplication] delegate] sharedOperationQueue] addOperation:operationToPerform];
[op release];
In a word: NSOperationQueue
NSOperationQueue is thread safe (you can add operations to it from different threads without the need for locks) and enables you to chain NSOp objects together.
My Flickr iPhone app, Reflections, uses NSOperation and NSOperationQueue extensively to manage downloading images and XML.
Caveat: Make sure you read, re-read, and understand what the docs mean when they talk about 'concurrency'.
You should also check out this URL:
http://developer.apple.com/cocoa/managingconcurrency.html
All these above answers are great, but make sure you read the article above and make liberal use of this line in your code:
if ( self.isCancelled ) return;
That line wasn't used in the samples provided by Coca is my Girlfriend, and it wasn't until I got crash logs in from the field that I realized this was an issue/concept.
Here is just a very simple implementation but take time to read the tutorials to fully understand everything:
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(methodToCall)
object:objectToPassToMethod];
[queue addOperation:operation];
I use it for asynchronous processing. It is the best way to get data from web services or to coordinate actions that take significant time to run. Because they are thread safe, asynchronous (doesn't tie up the main thread) and they support dependencies, they are a really great tool for your toolset.
Dependencies allow you to make several separate operations and make sure the execute and succeed or error out in a certain order. This is really great when you need to synchronize a bunch of data but you need parent objects to sync before syncing child objects.
A sample that you can try using Swift
let operation : NSOperation = NSOperation()
operation.completionBlock = {
println("Completed")
}
let operationQueue = NSOperationQueue.mainQueue()
operationQueue.addOperation(operation)