user6092898
user6092898

Reputation:

Loading a image using dispatch_async within cellForRowAtIndexPath

Im trying to load an image from an url.

Following is my code..

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

  UITableViewCell *cell = nil;

    cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@""];

    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:nil];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

    dispatch_async (dispatch_get_main_queue(), ^{

        NSData * storeImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:productImageArray[indexPath.row]]];

        self.productImage.image = [UIImage imageWithData:storeImageData];
        });

            [cell.contentView addSubview:self.productImage];

return cell;
}

The problem is that,

  1. UI freezes till the image is loaded.
  2. Only the last cell is loading the image but the remaining cells are not loading the image.

How can I sort this out?

Upvotes: 1

Views: 4250

Answers (6)

karthik
karthik

Reputation: 621

@MGR, you can use Dispatch async, but there was a library https://www.cocoacontrols.com/controls/hanekeswift, an excellent library. it will handle downloading images and it has a method to load the image in imageview.public func hnk_setImageFromURL(URL: NSURL, placeholder: UIImage? = default, format: Haneke.Format<UIImage>? = default, failure fail: ((NSError?) -> ())? = default, success succeed: ((UIImage) -> ())? = default)

it also has the cache feature. so you can simply implement this function in cellForRow. thats it.evertything will be handled by Haneke.

* PLEASE USE THIS FRAMEWORK * https://github.com/onevcat/Kingfisher

This brings better performance and customization than Hanekeswift

Upvotes: 1

UdayM
UdayM

Reputation: 1783

I feel this is the best approach for downloading images asynchronously. I will use below method for this task always.

In cellForRowAtIndexPath:

 NSURL *imgURL = [[NSURL URLWithString:productImageArray[indexPath.row]];
 [self downloadImageWithURL:imgURL
            completionBlock:^(BOOL succeeded, UIImage *image) {
                          self.productImage.image=image;
     }];

and add this method

- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
     [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
          completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
             if ( !error )
              {
                data = [NSData dataWithContentsOfURL:url];
                UIImage *image = [[UIImage alloc] initWithData:data];
                completionBlock(YES,image);
              } else{
                 NSLog(@"Error in downloading image:%@",url);
                 completionBlock(NO,nil);
              }
           }];
}

Using this,even we can know better if there is any problem while downloading image.

Upvotes: 2

gunjot singh
gunjot singh

Reputation: 2598

Your are downloading the images synchronously on main UI thread, which is causing the screen to freeze.

Please follow below article to resolve the issue:

iOS: How To Download Images Asynchronously (And Make Your UITableView Scroll Fast)

and also answer to your second point:

You seem to be using a single imageView instantiated outside the cellForRowAtIndexPath method, and then you are trying to add it in every cell, which will cause the image to be removed from previous cell, and added in current cell

because a view could have only one superview, and once you try to add a child view in other view, it will get removed from its current super view

Upvotes: 0

Vijay Kachhadiya
Vijay Kachhadiya

Reputation: 376

Try this one:

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    cell.imageView.image = image;  

Or sometimes what I do is to update the model and then reload the cell at the specific indexPath:

  myModel.image = downloadedThumbImage;
  [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                                  withRowAnimation:UITableViewRowAnimationNone];  


  - (UITableViewCell *)tableView:(UITableView *)tableView    cellForRowAtIndexPath:(NSIndexPath *)indexPath
  {
    static NSString *CellIdentifier = @"top50places";
    UITableViewCell *cell = [tableView   dequeueReusableCellWithIdentifier:CellIdentifier];

   //getting the selected row image 
  NSDictionary* currentImageDictionary=[self.topfifty objectAtIndex:indexPath.row];//topFifty is an array of image dictionaries

   UIImage *currentImage = [currentImageDictionary objectForKey:@"image"];

  if (currentImage) { 
      // we already fetched the image, so we just set it to the cell's image view
     cell.imageView.image = currentImage;
  }
  else {
       // we don't have the image, so we need to fetch it from the server  

   // In the meantime, we can set some place holder image
   UIImage *palceholderImage = [UIImage imageNamed:@"placeholder.png"];
   cell.imageView.image = palceholderImage;

  // set the placeholder as the current image to your model, so you won't 
  // download the image multiple times (can happen if you reload this cell while 
  // download is in progress)
  [currentImageDictionary setObject:palceholderImage forKey:@"image"];

  // then download the image
  // creating the download queue 
  dispatch_queue_t downloadQueue=dispatch_queue_create("thumbnailImage", NULL);

  dispatch_async(downloadQueue, ^{
     UIImage *downloadedThumbImage=[self getImage:currentImageDictionary] ;

     //Need to go back to the main thread since this is UI related
     dispatch_async(dispatch_get_main_queue(), ^{
            // store the downloaded image in your model
            [currentImageDictionary setObject:image forKey:@"image"];

            // update UI
            UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
            cell.imageView.image = image;    
     });
  });
  dispatch_release(downloadQueue);

   }

 return cell;
 }

Upvotes: 0

Elangovan
Elangovan

Reputation: 1206

You can use GCD to load images in background thread, like this:

  //get a dispatch queue

    dispatch_queue_t concurrentQueue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //this will start the image loading in bg

    dispatch_async(concurrentQueue, ^{        
        NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];

        //this will set the image when loading is finished

        dispatch_async(dispatch_get_main_queue(), ^{
            imageView.image = [UIImage imageWithData:image];
        });
    });

But The simplest fix that addresses these issues is to use a UIImageView category, such as is provided with SDWebImage or AFNetworking. If you want, you can write your own code to deal with the above issues, but it's a lot of work, and the above UIImageView categories have already done this for you.

Upvotes: 0

Bhavin Ramani
Bhavin Ramani

Reputation: 3219

Try NSURLSessionTask :

NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                UITableViewCell * cell = (id)[tableView cellForRowAtIndexPath:indexPath];
                if (cell)
                    cell. productImage.image = image;
            });
        }
    }
}];
[task resume];

Upvotes: 0

Related Questions