Correct usage of Grand Central Dispatch with multiple UIActivityIndicatorViews
I am creating a thumbnail viewer consisting of UIButtons, and want to have a spinner in each button until the image gets populated from the cloud. I've done this with single spinners before, but never en masse like this. The spinners aren't coming up (maybe a non-main thread UI issue?)...am I at least headed in the right direction? Thanks in advance.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_apply(numberOfThumbs, queue, ^(size_t i){
Post *post = [arrayOfPosts objectAtIndex:i];
UIActivityIndicatorView *newSpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
UIButton *currentThumb = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
[currentThumb setFrame:CGRectMake((105.5 * (i%3))+3.5, ((i/3)*105.5) + 3.5, 102, 102)];
[newSpinner setFrame:CGRectMake(54 - spinner.frame.size.width/2, 54 - spinner.frame.size.height/2, spinner.frame.size.width, spinner.frame.size.height)];
[currentThumb addSubview:newSpinner];
[newSpinner startAnimating];
[thumbna开发者_开发问答ils addObject:currentThumb];
dispatch_async(queue2, ^(void){
[currentThumb setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:post.thumbUrl]]] forState:UIControlStateNormal];
[newSpinner stopAnimating];
[newSpinner removeFromSuperview];
[newSpinner release];
});
[currentThumb release];
});
dispatch_release(queue);
dispatch_release(queue2);
Thanks to Daniel and Brad's suggestion, below, the following dance seems to work:
- (id)initWithArrayOfPosts:(NSArray*)posts
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
// Custom initialization
arrayOfPosts = [posts retain];
numberOfThumbs = [arrayOfPosts count];
oldDequeueFactor = 0;
oldYOffset = 0.0;
thumbnails = [[NSMutableArray alloc] initWithCapacity:15];
mainScrollView = [[UIScrollView alloc] init];
[mainScrollView setFrame:self.view.frame];
[mainScrollView setContentSize:CGSizeMake(self.view.frame.size.width, 109 * (ceilf((float)numberOfThumbs/3.0)))];
[mainScrollView setDelegate:self];
[mainScrollView setBackgroundColor:[UIColor whiteColor]];
[self setView:mainScrollView];
for (int i=0; i<numberOfThumbs; i++) {
UIActivityIndicatorView *newSpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
UIButton *currentThumb = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
[currentThumb setFrame:CGRectMake((105.5 * (i%3))+3.5, ((i/3)*105.5) + 3.5, 102, 102)];
[newSpinner setFrame:CGRectMake(54 - newSpinner.frame.size.width/2, 54 - newSpinner.frame.size.height/2, newSpinner.frame.size.width, newSpinner.frame.size.height)];
[currentThumb addSubview:newSpinner];
[newSpinner startAnimating];
[thumbnails addObject:currentThumb];
[currentThumb release];
[mainScrollView addSubview:[thumbnails objectAtIndex:i]];
[self getImageDataAsynchronouslyForThumbnail:[thumbnails objectAtIndex:i] forIndex:i andRemoveSpinner:newSpinner];
}
}
return self;
}
- (void)getImageDataAsynchronouslyForThumbnail:(UIButton*)thumb forIndex:(int)index andRemoveSpinner:(UIActivityIndicatorView *)spinner
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^(void){
Post *post = [arrayOfPosts objectAtIndex:index];
UIImage *thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:post.thumbUrl]]];
NSArray *args = [NSArray arrayWithObjects:thisImage, thumb, spinner, nil];
if ([args count] == 3) { //use this array to verify I have all info I need
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self setImage:[args objectAtIndex:0] ForThumbnail:[args objectAtIndex:1] andStopAnimatingSpinner:[args objectAtIndex:2]];
});
} else {
[self getImageDataAsynchronouslyForThumbnail:thumb forIndex:index andRemoveSpinner:spinner];
return;
}
});
dispatch_release(queue);
}
- (void)setImage:(UIImage*)image ForThumbnail:(UIButton *)thumb andStopAnimatingSpinner:(UIActivityIndicatorView *)spinner
{
[thumb setImage:image forState:UIControlStateNormal];
[spinner stopAnimating];
[spinner removeFromSuperview];
[spinner release];
}
Yes i believe your issue is that you are executing code from a background thread, UIKit isnt thread safe, so you should probably only get the image data in the background then use performSelectorOnMainTHread in order to set the image and stop animating, something like
-(void)setDataStopAnimating:(NSArray*)args
{
//get your arguments from the array and then run
[thumb setImage:[UIImage imageWithData:imageData] forState:UIControlStateNormal];
[newSpinner stopAnimating];
[newSpinner removeFromSuperview];
[newSpinner release];
}
//then in your queue code
dispatch_async(queue2, ^(void){
NSArray *args; //fill the array with the arguments
[self performSelectorOnMainThread:@selector(setDataAndStopAnimating:) withObject:args]
});
精彩评论