0

I have a problem with cellForRowAtIndexPath always returning nil when called from tableView:cellForRowAtIndexPath:

The thing is I am actually calling cellForRowAtIndexPath inside a block inside tableView:cellForRowAtIndexPath: and I guess that this might be the problem.

I am populating cells with remote images that load (and cache) ad hoc. When the image returns fully loaded in the completion handler block (inside tableView:cellForRowAtIndexPath:) I need to get the cell again because it might have scrolled out of view... So I call cellForRowAtIndexPath to get the cell again - cellForRowAtIndexPath would only return a cell if it's visible. In my case I never get it to return anything but nil. Even though I am scrolling very slowly (or fast, or whatever speed I tried...) all I get is nil.

I'll try to get some code in here soon but until then any suggestion why this would occur - I guess the block thing would be a hint.

Here is the code: https://dl.dropboxusercontent.com/u/147970342/stackoverflow/appsappsappsappsapps.zip

The -tableView:cellForRowAtIndexPath: implementation:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSNumber* numberAppleid = self.appids[indexPath.row];
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"Mycell" forIndexPath:indexPath];

    cell.textLabel.text = [NSString stringWithFormat:@"Appleid: %@", numberAppleid];

    NSString* appleidasstring = [NSString stringWithFormat:@"%@", numberAppleid];

    cell.imageView.hidden = YES; // Hide any previous until image loads
    [self getAppIconForAppleid:appleidasstring handlerImage:^(UIImage *image) {
        if (image == nil) {
            return;
        }

        DLog(@"cellForRowAtIndexPath tableView: %@, indexPath: %@", tableView, indexPath);
        UITableViewCell* localcell = [tableView cellForRowAtIndexPath:indexPath];
        if (localcell != nil) {
            localcell.imageView.image = image;
            localcell.imageView.hidden = NO;
        }
        else {
            DLog(@"Nah indexpath is not visible for: %@ because localcell is nil.", appleidasstring);
        }
    }];


    return cell;
}

Fixed version:

#define KEYAPPLEID  @"keyappleid"
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSNumber* numberAppleid = self.appids[indexPath.row];
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"Mycell" forIndexPath:indexPath];

    cell.textLabel.text = [NSString stringWithFormat:@"Appleid: %@", numberAppleid];

    NSString* appleidasstring = [NSString stringWithFormat:@"%@", numberAppleid];

    [cell setAssociativeObject:appleidasstring forKey:KEYAPPLEID];

    cell.imageView.hidden = YES; // Hide any previous until image loads
    [self getAppIconForAppleid:appleidasstring handlerImage:^(UIImage *image) {
        if (image == nil) {
            return;
        }

        NSString* currentappleidofcell = [cell associativeObjectForKey:KEYAPPLEID];
        if ([currentappleidofcell isEqualToString:appleidasstring]) {
            cell.imageView.image = image;
            cell.imageView.hidden = NO;
        }
        else {
            DLog(@"Returning appleid: %@ is not matching current appleid: %@", appleidasstring, currentappleidofcell);
        }
    }];


    return cell;
}

And this category is needed: https://stackoverflow.com/a/10319083/129202

Community
  • 1
  • 1
Jonny
  • 15,955
  • 18
  • 111
  • 232

1 Answers1

3

If the block is within the tableView:cellForRowAtIndexPath: callback, you can just reference the cell directly in the block. The block will automatically keep the cell around until the block goes away. However, be careful of retain cycles. If the block is owned by the cell, you will have to use a weak reference to the cell.

So your implementation would look like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSNumber* numberAppleid = self.appids[indexPath.row];

    // Create your own UITableViewCell subclass that has an "appleidasstring" property
    MyTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"Mycell" forIndexPath:indexPath];

    cell.textLabel.text = [NSString stringWithFormat:@"Appleid: %@", numberAppleid];

    NSString* appleidasstring = [NSString stringWithFormat:@"%@", numberAppleid];

    cell.imageView.hidden = YES; // Hide any previous until image loads

    // Set the appleidasstring on the cell to be checked later
    cell. getAppIconForAppleid:appleidasstring = appleidasstring;

    // Modify the handler callback to also callback with the appleidasstring
    [self
        getAppIconForAppleid:appleidasstring
        handlerImage:^(UIImage *image, NSString *imageAppleIdaString)
        {
            if (image == nil) {
                return;
            }

            // Ensure the cell hasn't been repurposed for a
            // different imageAppleIdaString
            if ([cell.appleidasstring isEqualToString:imageAppleIdaString]) {
                cell.imageView.image = image;
                cell.imageView.hidden = NO;
            }
        }
    ];

    return cell;
}
drewag
  • 93,393
  • 28
  • 139
  • 128
  • The thing is the cell cannot be reused because it might have scrolled out of view when the block returns. This is a common error and is a reason why the cell needs to be acquired again. But I will post the code shortly. – Jonny Apr 01 '14 at 03:39
  • This can be solved by running a check on the cell to ensure that it it is still representing the same image that the block just finished downloading. For example, by storing the URL to the image on the cell. – drewag Apr 01 '14 at 03:42
  • It sounds like something I should try next. I posted the code: https://dl.dropboxusercontent.com/u/147970342/stackoverflow/appsappsappsappsapps.zip – Jonny Apr 01 '14 at 03:44
  • Btw this is very related http://stackoverflow.com/questions/22631635/uitableviewcell-returning-as-nil-inside-block – Jonny Apr 01 '14 at 03:45
  • I just tried your suggestion by storing a unique value (in my case I work with Apple IDs of apps) to the cell, then checking that on return. Then I don't need to use `cellForRowAtIndexPath`, and I can use the local reference of the cell just like you're saying. This seems to work great so far. – Jonny Apr 01 '14 at 03:52
  • I just updated my answer with an example. You just need to make sure that you modify the handler to return the appleidasstring that was used to get the image. If you use the variable that you created outside the block, it will always come out true even if the cell has been repurposed. – drewag Apr 01 '14 at 03:58