I load a huge-huge image with imageWithContentsOfFile:, so I have to set up an activityIndicator during the process.
Is there any way/any delegate callback I can use to be informed about the end of this loading process?
imageWithContentsOfFile is synchronous.
You could start an activity indicator, load your big image into memory in a background thread and then go back to the main thread and stop the indicator.
- (void)loadBigImage {
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(loadBigImageInBackground) withObject:nil];
}
- (void)loadBigImageInBackground {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *img = [UIImage imageWithContentsOfFile:#"..."];
[self performSelectorOnMainThread:#selector(bigImageLoaded:) withObject:img waitUntilDone:NO];
[pool release];
}
- (void)bigImageLoaded:(UIImage *)img {
[activityIndicator stopAnimating];
// do stuff
}
Short answer: Nope. sorry!
Long answer :
You could open the file in a background process (an NSOperation?) bit by bit using C style methods i.e. fopen, fread etc) and fire notifications back to the main thread during the load. Then create the image and fire a notification that the image is ready?
If you want to have a delegate & be informed of the progress of the load, you can use an NSURLConnection instead of the synchronous imageWithContentsOfFile.
There's an example of this in the Apple URL Loading System Programming Guide
Your NSURLConnection delegate didReceiveData: method could append the incoming data to an NSData object, then you would use UIImage imageWithData: to create them image once everything's downloaded.
This gives you the most flexibility/control over monitoring the progress of the load; although if all you're trying to do is avoid hanging the UI while the image downloads, simply using imageWithContentsOfFile in a background thread may be easier.
Related
In my app, I load image from the URL:
-(void)loadImages
{
...
image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:imgUrl1]];
}
In order to avoid blocking the main thread until the download has completed I call this method in
-viewDidAppear using GCD:
dispatch_async( dispatch_get_global_queue(0,0), ^{
[self loadImages];
});
However, when I open my view controller with the imageView at the first time, the imageView is empty (even if I wait for a long time) but after I open this view controller again and image appears and everything is good to go.
Where is my mistake ?
Sorry, new to multithreading :)
EDIT:
I also forgot to mention, that I use the image in the tableView when I get it:
cell.imageView.image = image1;
This may not be the answer that you were looking for, but that's not the recommended way to load URLs. You should use the URL loading classes available such as NSURLRequest and NSURLConnection.
Try this:
NSURLRequest *imageRequest = [[NSURLRequest alloc] initWithURL:imageURL];
[NSURLConnection sendAsynchronousRequest:imageRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
UIImage *image = [[UIImage alloc] initWithData:data];
[imageView setImage:image];
}
}];
UIElements in iOS should always be updated via main thread.
What you could do is:-
__block NSData *data;
dispatch_queue_t myQueue = dispatch_queue_create("com.appName", NULL);
dispatch_async(myQueue, ^{
data = [NSData dataWithContentsOfURL:imgUrl1];
dispatch_async(dispatch_get_main_queue(), ^(void) {
cell.imageView.image = [UIImage imageWithData = data];
});
});
or Else you there is a better way of fetching images from URL by using AFNetworking. It is faster and easier.
You just have to write one line of code:-
[cell.imageView setImageWithURL:imgUrl1];
You have many problems here:
You can not start networking requests on an thread not running a runloop.
You can not update your UI from a thread other than the main thread.
[NSData dataWithContentsOfURL:imgUrl1] is not a safe way to load an external resource, even on a different thread (but especially on the main thread).
Any time you dispatch to a different thread, you run the risk that your table cell has been recycled and is no longer showing the data you think it is. (It's still the same cell instance, but is now showing some other row's data.)
What you should be doing:
Start your network operation using asynchronous calls on the main thread. (You can use another thread or queue if you want, but you need to make sure it's running a runloop.)
From your delegate messages, dispatch your image decoding on a different thread.
After the image is decoded, dispatch back to the main thread to update it.
Before actually assigning the image, check that the cell is still being used for the purpose you think.
You can solve the first three problems by using AFNetworking. It wraps the delegate methods and lets you just provide a success and failure block. AFNetworking's AFImageRequestOperation in particular bounces code between queues as I've described. (It even runs its main networking loop in a different thread, which isn't necessary but since it does it well, why not?)
You'll still need to verify the cell's identity.
You need to inform the view that the image needs to be redrawn. Add:
[imageView setNeedsDisplay];
to the end of your loadImages method.
Since you are using it in TableView, add [self.tableView reloadData]; in the end of loadImages method.
I have slideshow, and I want to show Big images, I added to [NSOperation mainQueue] operation with low priority, this operation shows the image.
If image is small , everything is OK, but when image is about 5Mb, the view freeze for 1 second, and I can't scroll my slideshow. I think, that displaying big images just so difficult for iPhone, that main queue is too overloaded.
But I don't inderstand it , because all my displaying code is executed in low priority operation.
Here is the displaying code.
[imageView removeFromSuperview];
imageView = nil;
// reset our zoomScale to 1.0 before doing any further calculations
self.zoomScale = 1.0;
// make a new UIImageView for the new image
self.imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
[self addSubview:imageView];
self.contentSize = [image size];
[self setMaxMinZoomScalesForCurrentBounds];
self.zoomScale = self.minimumZoomScale;
May be I can set the priority for gesture recognizers (the regular questure recognizers for UIScrollView?)
Update
Please look at my new topic, I described the issue more properly my topik
Priority has to do with scheduling. If you queue up a bunch of operations during a runloop iteration then they will be executed by their priority on that queue.
One solution to speed this up would be to either include resources that are scaled to the exact size that you are displaying them in. If you are trying to show a 2000x2000 px image in a 200x200 area then the system to scale all this stuff in memory. You can also dynamically create smaller to fit images programmatically. This can be done on a background queue so your UI is still responsive.
How to resize the image programmatically in objective-c in iphone
If I understand you correclty and you have done something like
NSOperationQueue* queue = [NSOperationQueue mainQueue];
Then any NSOperation you add to it will execute on the main dispatch queue which is concurrent (and responsible for executing tasks on the main thread). That would explain the freeze. You can create your own queue which would start a thread for every NSOperation and would free the main thread to render the UI normally as:
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
This however would cause a problem. When the image is finished loading and you pass it to an UIImageView on the screen there will be a big delay until the Image is actually rendered because the main (UI) thread will not be aware of the action until it chooses to refresh (a few seconds later). The solution to this is to add a 'performInMainThread' message to the end of the main method of the NSOperation as such:
-(void)main {
NSData *bgImageData = [NSData dataWithContentsOfURL:self.targetUrl];
UIImage *img = [UIImage imageWithData:bgImageData];
[self performSelectorOnMainThread:#selector(insertImageLoaded:)
withObject:img
waitUntilDone:YES];
}
The 'insertImageLoaded' is in the NSOperation and would load call a setImage:(UIImage*)img to the component you want.
i have a big problem and i need your help. Here's what i need to accomplish:
The user select a row from a
TableView
A new view controller is pushed in
the NavigationController, and
displays only a "Loading" message
Meanwhile some data is read from an
XML file (via http)
When the data has been read, an
NSUConnection is used to load an
image from an URL (this URL is part
of the data)
While the image is still loading,
the other data is displayed on the
screen
The image has been downloaded and is
shown, completing the appearance of
the view
The big problem is that i can't use detachNewThreadSelector and NSURLConnection together!
So how can i make a workaround for this? How would you do this?
Thank you VERY much!
You can use following approach...(if you are using asynchronous request)
When your application comes in - (void)connectionDidFinishLoading:(NSURLConnection *)connection ... add a NSInvocationOperation object in NSOperationQueue (which you can handle at application level, by synthesizing it in appDelegate) ..
create NSInvocationOperation as follows..(in connectionDidFinishLoading)
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(parseIt) object:nil];
[appDelegate.operationQueue addOperation:operation];
[operation release];
-(void) parseIt
{
//ask for parsing stuff....what you have earlier wrote directly in connectionDidFinishLoading
}
Thanks,
I would use a NSTimer to solve the problem using detachNewThreadSelector and NSURLConnection together.
I have similar scenario where there is a downloading Progress UIViewController showing till the file getting complete, here is what i do:
I Draw a loading View contains a Activity Indicator for example.
I initialize a NSTimer to keep checking if the file is complete.
I call the method that contains the Download Logic.
[1]
-(void) vManageFileRequest
{
[self.oFilesManager vGetSingleFileRequest];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(vValidateFileRequest) userInfo:nil repeats:NO]];
}
[2]
[self performSelectorOnMainThread:#selector(vManageFileRequest) withObject:nil waitUntilDone:NO];
I know there are lots of similar questions floating around, but none of the answers seem to fix my problem. I have an app that uses an NSURLConnection to download a file, and then does some calculations on the downloaded file. I set up a UILabel to display the current loading status (eg: "Loading file", "Parsing file"). I update the UILabel in the didReceiveResponse and connectionDidFinishLoading function of the NSURLConnection delegate, as well as some other places in my code. I update it by calling the following function:
[self performSelectorOnMainThread:#selector(updateProgress) withObject:nil waitUntilDone:NO]
where -(void)updateProgress is a function I defined to call [theLabel setNeedsDisplay]. I NSLog'd it, like
NSLog(#"theLabel: %#\n",theLabel.text);
and the information is updated correctly, but the label doesn't actually update in the view. Also, updateProgress is only called AFTER everything is loaded. It updates the label THEN, which is hardly useful. Any suggestions?
The NSURLConnection is blocking the main thread (no updates will be performed on the view until it finishes).
You can perform updateProgress in the background:
[self performSelectorInBackground:#selector(updateProgress) withObject:nil]
The first line of updateProgress should be:
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc]init];
The last lines should be:
[pool release];
pool = nil;
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
Of course, you can also perform the NSURLConnection in the background. Then you can update the label on the main thread.
I am downloading an mp3 using NSData dataWithContentsOfURL:url. This takes a while and while the file is downloading the application hangs. I want to handle well and ideal would like to show the download progress but can't find methods for this.
It is in a UIViewController and I have made a first attempt by putting in a UIActivityIndicatorView and start it spinning before I start the download, then stop it spinning after but nothing appears.
So my question really is please could someone tell me what the best way to handle this is? Thanks so much
Nothing will appear because your main thread is blocked doing the download, and the main thread is where UI updates occur.
You should use NSUrlConnection to download asynchronously and implement the delegate methods to start/stop your spinner.
Alternatively if you want to stick with NSData's dataWithContentsOfURL:url you should do this on a separate thread and update the spinner on the main thread before and after you call it.
You can achieve this while still using synchronous methods, but you need to give the run loop a chance to start animating the activity indicator before you start the download.
You can achieve this by using either performSelector:withObject:afterDelay: with delay 0 to put a run loop between your animation start and the download, or (worse style, more risky) you can directly invoke the run loop within your code.
Sample code:
- (void)loadPart1 {
activityIndicator = [[[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIA...StyleGray]
autorelease];
activityIndicator.frame = myFrame;
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
[self performSelector:#selector(loadPart2) withObject:nil afterDelay:0];
}
- (void)loadPart2 {
[NSURLConnection sendSynchronousRequest:request returningResponse:&response
error:&error];
[activityIndicator stopAnimating];
}
More details here:
http://bynomial.com/blog/?p=15
(scroll down to Solution 1 or Solution 2).