I am making an app that shows an animated UIImageView as a custom way of indicating that the app is busy. I'm using an NSOperationQueue for file uploads, and I'd like the UIImageView to be shown when there is something in the queue. When every operation in the queue completes, I want to remove the UIImageView.
I thought that this is something really easy to do, but I've been stuck now for the past hour. Showing the UIImageView is really easy, but I can't seem to remove it. It's probably something really simple that I'm just overlooking. Here's my code. Thank you! :)
- (void)viewDidLoad {
//set up the uiimageview
self.spinnerView = [[UIImageView alloc] initWithFrame:CGRectMake([[UIScreen mainScreen] bounds].size.width-44,0,44,44)];
self.spinnerView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:@"0.gif"],
[UIImage imageNamed:@"1.gif"],
[UIImage imageNamed:@"2.gif"],
[UIImage imageNamed:@"3.gif"],
[UIImage imageNamed:@"4.gif"], nil];
self.spinnerView.animationDuration = 0.5f;
self.spinnerView.tag = 998;
self.spinnerView.animationRepeatCount = 0;
[self.view addSubview: self.spinnerView];
//set up the queue
self.uploadQueue = [[NSOperationQueue alloc] init];
[self.uploadQueue setMaxConcurrentOperationCount:1];
//set up observer for the queue
[self.uploadQueue addObserver:self forKeyPath:@"operationCount" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)newUpload:(NSData*)data {
[self.spinnerView startAnimating];
//....
//request is a NSURLRequest that's set up in this method
[NSURLConnection sendAsynchronousRequest:request queue:self.uploadQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
}];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (object == self.uploadQueue && [keyPath isEqualToString:@"operationCount"]) {
if (self.uploadQueue.operationCount == 0) {
[self.spinnerView stopAnimating];
}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
Am I doing this correctly? Is there a better way to do it? I've been stuck here for a while and am starting to think that perhaps it's not the UIImageView that's messing up, but rather the way that I'm adding NSURLRequests to the NSOperationQueue.
Thanks again!
The documentation for sendAsynchronousRequest:queue:completionHandler:
says that an operation is only added to the specified operation queue after the asynchronous URL request has completed. This operation is just a completion block.
So I do't think you are really adding adding operations in the way you intend to your queue. The URL requests will be running on their own threads outside the queue, only the completion blocks are put on the queue. If you haven't specified anything in the completion block itself then perhaps it is not even added to the queue at all?
Either way I don't think you are adding 5 URL operations to the queue, which then execute one after the other with operationCount
== 5, 4, 3, 2, 1, 0. You are more likely firing 5 simultaneous URL requests with their completion blocks being added to the queue in an indeterminate sequence after their URL requests happen to finish.
To do what I think you intend to do, you could:
NSOperation
subclass that contains and
manages an NSURLConnection
and NSURLRequest
etc. AFNetworking
. sendAsynchronousRequest:queue:completionHandler:
but use the
completion handler of one operation to start the next request, and
the completion handler of the final request to stop the spinner.
You could just use the main queue here as the only work being done
in the queue is starting the next operation or stoping the spinner. The actual work of the URL Request is done on it's own thread anyway. Writing your own concurrent NSOperation is a bit tricky, I wrote one myself, but I probably should have just used AFNetworking. Option 3 is probably the quickest if it meets your needs.