Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

mikezang

macrumors 6502a
Original poster
May 22, 2010
939
41
Tokyo, Japan
I have at able view and I will download images with blocks for all cells, The connection indicator is showed in downloading, I want to know how can I hide indicator when all images finished to download.

Does anyone give some suggestion?
 
You can keep a count (ivar/property) of the total images downloaded. If a download block success, you increment the count. Then you check if the count equals the total cells in your table. If, yes, you hide the indicator.
 
You can keep a count (ivar/property) of the total images downloaded. If a download block success, you increment the count. Then you check if the count equals the total cells in your table. If, yes, you hide the indicator.
I use code as below to download images, I found there is problem but I am not sure where is, can you give some sueesgtion?
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"searchResultCell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }
    
	cell.textLabel.text = [self.searchResultsTrackName objectAtIndex:indexPath.row];
	cell.imageView.image = [self.cachesIconImage objectForKey:[NSNumber numberWithInt:indexPath.row]];
    
    if (!cell.imageView.image) {
        cell.imageView.image = [UIImage imageNamed:@"Icon.png"];
        
        dispatch_async(kBgQueue, ^{
            NSString *iconURL = [self.searchResultsIconURL objectAtIndex:indexPath.row];
            NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];

            dispatch_async(dispatch_get_main_queue(), ^{
                UIImage *icon = [UIImage imageWithData:imageData];
                [self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:indexPath.row]];
                cell.imageView.image = icon;
            });
        });
    }
    
    [cell setNeedsLayout];
    return cell;
}

- (void)fetchedData:(NSData *)responseData {
    NSError *error;
    
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
    
	self.searchResults = [json objectForKey:@"results"];
    
    self.searchResultsTrackName = [self.searchResults valueForKey:@"trackName"];
    self.searchResultsIconURL = [self.searchResults valueForKey:@"artworkUrl60"];
    
    [self.searchTableView reloadData];
    
    [self downloadImages];
}

- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }

    NSUInteger count = self.searchResultsIconURL.count;
    
    for (NSUInteger index = 0; index < count; index++) {
	    dispatch_async(kBgQueue, ^{
            NSString *iconURL = [self.searchResultsIconURL objectAtIndex:index];
        	NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];

        	dispatch_async(dispatch_get_main_queue(), ^{
        		UIImage *icon = [UIImage imageWithData:imageData];
            	[self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
            });
        });
    }
    
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
 
Try this in your download method:

Code:
- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }

    NSUInteger count = self.searchResultsIconURL.count;
    
    for (NSUInteger index = 0; index < count; index++) {
            static int i = 0;
	    dispatch_async(kBgQueue, ^{
            NSString *iconURL = [self.searchResultsIconURL objectAtIndex:index];
        	NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                i++;
                if (i == count - 1) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    });
                }

        	dispatch_async(dispatch_get_main_queue(), ^{
        		UIImage *icon = [UIImage imageWithData:imageData];
            	[self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
            });
        });
    }
    
}
 
Last edited:
Try this in your download method:

Code:
- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }

    NSUInteger count = self.searchResultsIconURL.count;
    
    for (NSUInteger index = 0; index < count; index++) {
	    dispatch_async(kBgQueue, ^{
            NSString *iconURL = [self.searchResultsIconURL objectAtIndex:index];
        	NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                if (index == count - 1) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    }
                }

        	dispatch_async(dispatch_get_main_queue(), ^{
        		UIImage *icon = [UIImage imageWithData:imageData];
            	[self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
            });
        });
    }
    
}
Do you think downloadImages and fetchData methods are no problems? I found sometimes downloadImges doesn't work, especially in 2nd time to run it! Does async need to be released? or any bug else? I am not sure about blocks...
 
Well, they look fine, but I do not understand why you are again downloading the image in cellForRowAtIndexPath
At first, I just download in cellForRow, then I used downloadImages to cache them, but I thought it might be possible when a cell is displayed but that image was not yet downloaded so I keep downloading in cellForRow, is it right?
 
Ideally you should lazy load the images, i.e., what you have done in your cellForRowAtIndexPath method. But this does not notify when all images are done downloading.

The downloadImages method is required so that you know when all images are finished downloading. You should set the cell's image from your cache (if it exists), instead of downloading it, so that you don't download the same image twice.
 
Try this in your download method:
Code:
- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }

    NSUInteger count = self.searchResultsIconURL.count;
    
    for (NSUInteger index = 0; index < count; index++) {
            static int i = 0;
	    dispatch_async(kBgQueue, ^{
            NSString *iconURL = [self.searchResultsIconURL objectAtIndex:index];
        	NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                i++;
                if (i == count - 1) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    });
                }

        	dispatch_async(dispatch_get_main_queue(), ^{
        		UIImage *icon = [UIImage imageWithData:imageData];
            	[self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
            });
        });
    }
    
}
I tried you code, but seems like the indicator hide before all images downloaded, what do you think about this?
 
I change your code as below, now it does work in Simulator, but when I tried it on iPhone4S, downloadImages seems like not be executed, do you know why?

Code:
- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }
    
    NSUInteger count = self.searchResultsIconURL.count;
   [B]__block NSUInteger downloaded = 0;[/B] 
    
    for (NSUInteger index = 0; index < count; index++) {
        if ([self.cachesIconImage objectForKey:[NSNumber numberWithInt:index]] == nil) {
            dispatch_async(kBgQueue, ^{
                NSString *iconURL = [self.searchResultsIconURL objectAtIndex:index];
                NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    UIImage *icon = [UIImage imageWithData:imageData];
                    [self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
                    
                    [b]if (downloaded == count - 1) {
	                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    }
                    
                    downloaded++;[/b]
                });
            });
        }
    }
}
 
I change your code as below, now it does work in Simulator, but when I tried it on iPhone4S, downloadImages seems like not be executed, do you know why?

Code:
- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }
    
    NSUInteger count = self.searchResultsIconURL.count;
   [B]__block NSUInteger downloaded = 0;[/B] 
    
    for (NSUInteger index = 0; index < count; index++) {
        if ([self.cachesIconImage objectForKey:[NSNumber numberWithInt:index]] == nil) {
            dispatch_async(kBgQueue, ^{
                NSString *iconURL = [self.searchResultsIconURL objectAtIndex:index];
                NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    UIImage *icon = [UIImage imageWithData:imageData];
                    [self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
                    
                    [b]if (downloaded == count - 1) {
	                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    }
                    
                    downloaded++;[/b]
                });
            });
        }
    }
}

Strange, have you checked it by placing a breakpoint in downloadImages: to see if it is ever being called? Also, the use of block variable looks correct to me, although I haven't played with blocks for a while.
 
Strange, have you checked it by placing a breakpoint in downloadImages: to see if it is ever being called? Also, the use of block variable looks correct to me, although I haven't played with blocks for a while.
I just test, downloadImages is executed, but code as below not be run, I don't know why, maybe due to blocks..
Code:
dispatch_async(kBgQueue, ^{
}];
 
I just test, downloadImages is executed, but code as below not be run, I don't know why, maybe due to blocks..
Code:
dispatch_async(kBgQueue, ^{
}];

Does this work?

Code:
- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }
    
    NSUInteger count = self.searchResultsIconURL.count;
   __block NSUInteger downloaded = 0; 
   __block blockSafeSelf = self;  
    for (NSUInteger index = 0; index < count; index++) {
        if ([self.cachesIconImage objectForKey:[NSNumber numberWithInt:index]] == nil) {
            dispatch_async(kBgQueue, ^{
                NSString *iconURL = [blockSafeSelf.searchResultsIconURL objectAtIndex:index];
                NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    UIImage *icon = [UIImage imageWithData:imageData];
                    [blockSafeSelf.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
                    
                    if (downloaded == count - 1) {
	                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    }
                    
                    downloaded++;
                });
            });
        }
    }
}
 
Does this work?

Code:
- (void)downloadImages {
    if (self.cachesIconImage == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }
    
    NSUInteger count = self.searchResultsIconURL.count;
   __block NSUInteger downloaded = 0; 
   __block blockSafeSelf = self;  
    for (NSUInteger index = 0; index < count; index++) {
        if ([self.cachesIconImage objectForKey:[NSNumber numberWithInt:index]] == nil) {
            dispatch_async(kBgQueue, ^{
                NSString *iconURL = [blockSafeSelf.searchResultsIconURL objectAtIndex:index];
                NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    UIImage *icon = [UIImage imageWithData:imageData];
                    [blockSafeSelf.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
                    
                    if (downloaded == count - 1) {
	                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    }
                    
                    downloaded++;
                });
            });
        }
    }
}
Your code doesn't work, I got the same result as before....
 
Your code doesn't work, I got the same result as before....

This is very weird. I'm sorry to ask again, but if you add a breakpoint __inside__ the block of the dispatch function, it never halts there?

Also, are you sure that this returns nil?
Code:
[self.cachesIconImage objectForKey:[NSNumber numberWithInt:index]]

Also, can you try running both blocks on the same (main) thread, instead of a background queue?
 
This is very weird. I'm sorry to ask again, but if you add a breakpoint __inside__ the block of the dispatch function, it never halts there?

Also, are you sure that this returns nil?
Code:
[self.cachesIconImage objectForKey:[NSNumber numberWithInt:index]]

Also, can you try running both blocks on the same (main) thread, instead of a background queue?
I tried code as below, almost time "Finished download" in thread main not displayed, but some time (very rarely) is displayed.
Code:
- (void)downloadImages {
    if (cachesIconImage_ == nil) {
        self.cachesIconImage = [[NSMutableDictionary alloc] initWithCapacity:10];
    }
    
    for (NSUInteger index = 0; index < countImages; index++) {
        if ([self.cachesIconImage objectForKey:[NSNumber numberWithInt:index]] == nil) {
            NSLog(@"Sartt download %i", index);
            
            dispatch_async(kBgQueue, ^{
                NSString *iconURL = [self.searchResultsIconURL objectAtIndex:index];
                NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:iconURL]];
                NSLog(@"icon url= %@", iconURL);
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"Finished download");
                    UIImage *icon = [UIImage imageWithData:imageData];
					[self.cachesIconImage setObject:icon forKey:[NSNumber numberWithInt:index]];
                });
            });
        }
    }
}
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.