views:

1396

answers:

2

Hello Folks,

I was in the process of writing a threaded UITableView that retrieves some information from the net. I would like to display an activity indicator in place of the regular cell.image while the images are loading.

The problem is that the custom label and activity indicators are not behaving properly. The labels start out in the proper position, the activity indicator shows up in the proper position and begins to animate, but as soon as the first callback is hit on the image download thread (only one image is loaded, basically), the views all reset as if there were a single reference and I was resetting the frame on all of them...

I have overloaded - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

The code in question is here:

// Set up the cell...
    if( ![[cell contentView] viewWithTag:indexPath.row+200] ){
     UILabel *newLabel = [[UILabel alloc] init];
     newLabel.text = [[personList objectAtIndex:indexPath.row] name];
     newLabel.frame = [[cell contentView] frame];
     [[cell contentView] addSubview:newLabel];
     newLabel.tag = indexPath.row+200;
     [newLabel release];
    }
    else{
     UILabel *alreadySetLabel = (UILabel *)[[cell contentView] viewWithTag:indexPath.row+200];
     alreadySetLabel.text = [[personList objectAtIndex:indexPath.row] name];
     alreadySetLabel.frame = [[cell contentView] frame];
    }

    UIImageView *tmpImageView = cell.imageView;

    NSLog( @"In cell name: %@", [[personList objectAtIndex:indexPath.row] name] );
    NSLog( @"In cell profile image: %@", [[personList objectAtIndex:indexPath.row] profileImage] );

    if( [[personList objectAtIndex:indexPath.row] profileImage] ){
     tmpImageView.image = [[personList objectAtIndex:indexPath.row] profileImage];
     [[[cell contentView] viewWithTag:indexPath.row+100] removeFromSuperview];
     UILabel *labelRef1 = (UILabel *)[[cell contentView] viewWithTag:indexPath.row+200];
     [labelRef1 setFrame:CGRectMake( cell.contentView.frame.origin.x+70, cell.contentView.frame.origin.y, labelRef1.frame.size.width, labelRef1.frame.size.height)];
    }
    else{
     NSLog( @"%@", [[personList objectAtIndex:indexPath.row] imageFilePath] );
     if( ![[cell contentView] viewWithTag:indexPath.row+100] && [[personList objectAtIndex:indexPath.row] imageFilePath] ){
      UIActivityIndicatorView *newSpin = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
      [newSpin setTag:indexPath.row+100];
      [newSpin startAnimating];
      [newSpin setFrame:CGRectMake( 5, ((cell.contentView.frame.size.height/2)-7), 30, ((cell.contentView.frame.size.height/2)-7)+15) ];
      [[cell contentView] addSubview:newSpin];
      [newSpin release];
      UILabel *labelRef2 = (UILabel *)[cell viewWithTag:indexPath.row+200];
      [labelRef2 setFrame:CGRectMake( cell.contentView.frame.origin.x+70, cell.contentView.frame.origin.y, labelRef2.frame.size.width, labelRef2.frame.size.height)];
     }
    }

Brand new to iphone application development, less than two weeks in. I'm wondering if I am misunderstanding the use of tags? It seemed to me like if you could link the tag value to the indexPath, you could come up with unique identifiers to refer to the custom views? 100 and 200 are arbitrary values, such that I won't accidentally reuse the tags within a single screenful of data -- don't know if thats necessary, as I thought that tags only searched through the subviews.

Thanks in advance for any advice. Also open to general tips on how to make this more elegant...

Thanks, Josh

+1  A: 

I'm not sure what exactly you mean by "the views all reset as if there were a single reference and I was resetting the frame on all of them", but let me point some things out wrt. use of tags and loading table view cell images in general.

Tags are just a way to identify/retrieve subviews within one view - they don't have to be globally unique. Actually, incorporating indexPath.row in the tag value is not a good idea at all, because table view cells get reused (as long as you use dequeueReusableCellWithIdentifier:, which you definitely should do). Moreover, I would define the tag values as constants (eg. #define) and only reference those constants within your code. This way your code is more descriptive, less error-prone and much more maintainable.

To be honest, I don't like using tags at all. In deed, constructing table view cells "on the fly" in tableView:cellForRowAtIndexPath: is the only scenario I came across in which there is just no alternative (because you don't have any named outlets). I would recommend you to subclass UITableViewCell rather than messing with tags. It's really no big deal, but your code will be much cleaner and you can easily encapsulate all the cell related stuff within that class - which is especially important when you do more complex things like loading table view cell images asynchronously over the network.

Regarding the asynchronous image loading stuff, have you had a look at this project on github? It's basically a demo project to demonstrate how to load (flickr) images asynchronously and update them in a table view when finished loading - not a perfect implementation, but at least a good starting point. I have done the same thing recently, but my approach was to subclass UIImageView and do all the async loading/updating in this subclass. The main reason was that I wanted a generic and reusable solution.

Daniel Rinser
A: 

You are right -- subclassing the cell would have been the way to go as far as encapsulating and amount of code. I'll have to change that.

On a side note, I solved the problem with one additional branch of code.

To clarify, the problem was that the custom label I added to the subview was resetting its frame each time the table would reloadData. You'll notice I move it over 70 pixels to give room for the activity indicator or the image that has been loaded. It would do this the first time through, and then reset to origin.x = 0 every time an additional picture came back from being loaded on a background thread.

Apparently, the labels frame will be reset no matter what? My code would detect a subview in the cells contentView with tag of 100 (I changed it to the static value as you suggested and as I had before), and do nothing, as I was under the impression that the rect would stay static as long as the view existed. This was not the case, I had to explicitly set the frame no matter if it exists already or not. Anyway, probably just my not understanding whats going on under the hood fully. As soon as I added an additional setFrame in an else, everything worked fine.

Thanks, Josh

Josh