How to know downloading image finished in all table view cells?

Discussion in 'iOS Programming' started by mikezang, May 5, 2012.

  1. mikezang macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #1
    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?
     
  2. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #2
    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.
     
  3. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #3
    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;
    }
    
    
     
  4. CodeBreaker, May 5, 2012
    Last edited: May 5, 2012

    CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #4
    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]];
                });
            });
        }
        
    }
    
     
  5. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #5
    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...
     
  6. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #6
    Well, they look fine, but I do not understand why you are again downloading the image in cellForRowAtIndexPath
     
  7. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #7
    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?
     
  8. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #8
    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.
     
  9. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #9
    That is what I did in cellForRow, I only download image that is not yet downloaded (not yet cached).
     
  10. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #10
    That's true, but you also have to check in downloadImages: if a cell has already downloaded the image.
     
  11. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
  12. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #12
    I tried you code, but seems like the indicator hide before all images downloaded, what do you think about this?
     
  13. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #13
    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]
                    });
                });
            }
        }
    }
    
     
  14. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #14
    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.
     
  15. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #15
    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, ^{
    }];
     
  16. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #16
    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++;
                    });
                });
            }
        }
    }
    
     
  17. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #17
    Your code doesn't work, I got the same result as before....
     
  18. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #18
    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?
     
  19. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #19
    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]];
                    });
                });
            }
        }
    }
     
  20. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #20
    I'm sorry I really can't tell where it is going wrong. If you are not doing this to learn GCD/blocks, I recommend you use a third party library for your cell image views. The best one perhaps is UIImageView+AFNetworking.

    https://github.com/AFNetworking/AFNetworking
     

Share This Page