brcebn
brcebn

Reputation: 1732

Asynchronous loading of image in UITableViewCell

I would like to refresh a cell where an asynchrone loading has been done.

I know 2 ways to do that but none is exactly what I want. Here is what I know:

[myTableView reloadData];

Or

 [myTableView beginUpdates];
 [myTableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPathOfMyCell] withRowAnimation:UITableViewRowAnimationNone];
 [myTableView endUpdates];

My asynchrone method to load images is called directly from my custom UITableViewCell. That means it's independent of my UITableView. The final solution I have is to pass the my UITableViewCell as parameter with the index on the current cell. It looks like a very ugly solution.

Does anyone know another solution ?

Edit:

[myCacheClass loadUIImageFromPath:photo.mediumDistURL];

loadUIImageFromPath:(NSString *) path{
  //Some code to load the image.
  if(![weakSelf.delegate respondsToSelector:@selector(MyCacheDidLoadImageFromCache:)])
    [weakSelf setDelegate:appDelegate.callbacksCollector];
  [weakSelf.delegate MyCacheDidLoadImageFromCache:image];
 }

The implementation of MyCacheDidLoadImageFromCache:

- (void) CacheCacheDidLoadImageFromCache: (UIImage *) image{
  DDLogInfo(@"Successfully load image from cache : %@", [image description]);
  myImageView.image = image;
}

Upvotes: 1

Views: 1488

Answers (5)

Bryan
Bryan

Reputation: 12200

You can indirect via NSNotificationCenter. The table can subscribe notifications:

[[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(dataAvailableNotification:) 
    name:@"MyDataNowAvailable"
    object:nil];

When the data is available, the cache will post:

[[NSNotificationCenter defaultCenter] postNotificationName:@"MyDataNowAvailable" 
    object:self userInfo:@{@"Data": theData}];

Then the table reacts to this notification by mapping the data to appropriate cells and triggering the repaint:

- (void) dataAvailableNotification:(NSNotification *) notification
{
    DataObject *theData = notification.userInfo[@"Data"];
    // add code here to find which cell(s) this data is displayed in
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    // etc
}

Good thing about this method is it generalizes to situations where you have the same data displayed in multiple views.

Upvotes: 1

Thanh-Nhon Nguyen
Thanh-Nhon Nguyen

Reputation: 3428

So here is the block based solution, no need to pass tableView and indexPath pointer to cell. We'll implement a completionBlock instead. You should be familiar with this type of approach:

CustomCell.h:

// Define block as a typedef
typedef void (^CellCompletionHandler)(BOOL);
@interface MyTableViewCell : UITableViewCell

// Take a predefined block as a parameter
- (void)initWithImageURL:(NSURL *)URL completionHanlder:(CellCompletionHandler)completion;

@end

CustomCell.m:

- (void)initWithImageURL:(NSURL *)URL completionHanlder:(CellCompletionHandler)completion
{
    // Take the URL and call cell's inner loading method
    // Pass the completion block to it
    [self doSomethingWithCellCompletionHandler:completion];
}

- (void)doSomethingWithCellCompletionHandler:(CellCompletionHandler)completion
{
    // When you finish loading your image from cache
    // call this to tell the block that you finished
    completion(YES);
}

cellForRowAtIndexPath:

[cell initWithImageURL:[NSURL URLWithString:@"your URL"]completionHanlder:^(BOOL finishedLoading) {
        if(finishedLoading)
        {
            // Here you can use indexPath to check whether it's visible
            // in order to reload it
            NSLog(@"Finished loading image");
        }

    }];

Upvotes: 0

demonofthemist
demonofthemist

Reputation: 4199

You dont need to pass UITableViewCell, instead past NSIndexPath for that perticular cell. & then use reloadRowsAtIndexPaths method to reload.

To obtain the table container in your chache class file use property.

@property (nonatomic, assign) UITableView *tableView;

Then in implemetation

loadUIImageFromPath:(NSString *) path forCell(NsIndexPath*)indexPath{
  //Some code to load the image.
  if(![weakSelf.delegate respondsToSelector:@selector(MyCacheDidLoadImageFromCache:)])
    [weakSelf setDelegate:appDelegate.callbacksCollector];
  [weakSelf.delegate MyCacheDidLoadImageFromCache:image forCell:indexPath];
 }

- (void) CacheCacheDidLoadImageFromCache: (UIImage *) image forCell(NSIndexPath*)indexPath{
  DDLogInfo(@"Successfully load image from cache : %@", [image description]);
  [tableView beginUpdates];
  [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
  [tableView endUpdates];
  myImageView.image = image;
  //tableView is from property & you have to pass NSIndexPath
}

& in your ViewController assign this propery as

myCacheClass.tableView = yourTableView;
[myCacheClass loadUIImageFromPath:photo.mediumDistURL forCell:indexPath];

Upvotes: 0

Inertiatic
Inertiatic

Reputation: 1270

Put this in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { after you setup your subclassed TableViewCell.

        NSURL *imageURL = [NSURL URLWithString:@"your image url"];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            NSData *imageData = [NSData dataWithContentsOfURL:imageURL];

            if (imageData) {
                UIImage *image = [UIImage imageWithData:imageData];

                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        YourCellSubclass *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];

                        if (updateCell) {
                            updateCell.yourImageView.image = image;
                        }
                    });
                }
            }
        });

In this code, every cell makes an asynchronous image request as the cell is being loaded. If data comes back, it attempts to create a UIImage out of it. If that image is valid it then updates the image on the main thread only if the cell is still visible.

This code will make a network call for the image every time the cell is loaded. You will want to implement some sort of caching most likely. The type/amount of caching you want to do depends on how dynamic your data is.

Upvotes: 0

ajay
ajay

Reputation: 3345

There are plenty of better solutions already defined for this type of situation. You can go for sophisticated third party code AsyncImageView, AFNetworking.

Upvotes: 1

Related Questions