tags:

views:

103

answers:

1

We couldn't find a way to animate UIViews inside a UITableCell as an action from a callback.

Suppose in a scenario where you click on a button on the UITableViewCell and it fires off an asynchronous action to download a picture. Suppose further that when the picture is downloaded we want a UIView in the cell to animate the picture to give user a visual feedback that something new is about to be presented.

We couldn't find a way to track down the UIVIew to invoke beginAnimation on because the original cell that the user clicked on might now be used for another row due to the nature of cells being reused when you scroll up and down in the table. In other words we can't keep a pointer to that UITableViewCell. We need to find another way to target the cell and animate it if that row is visible and don't animate if the row is scrolled out of range.

A: 

Keep the cell object different from the object being animated so the cell holds a UIView. When the animation callback occurs check to make sure that the UIView still exists and, if it does, animate the changes.

When the cell object gets bumped off the screen and recycled, release the UIView that would have been animated and create a new one. When the animation callback occurs it will have nothing to do because the UIView no longer exists.

A modification of the above is to keep some sort of object in the UIView that your callback can check to see if the animation is still appropriate. This could be some sort of unique identifier for the picture being downloaded. If the identifier changes, no animation is needed. If it matches, do the animation.


EDIT:

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

    static NSString *MyIdentifier = @"MyTableCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                       reuseIdentifier:MyIdentifier] autorelease];
    } else {
        UIView *oldViewToAnimate = [cell.contentView viewWithTag:1];
        [oldViewToAnimate removeFromSuperview];
    }

    UIView *viewToAnimate = [[UIView alloc] initWithFrame:CGRectZero]; //replace with appropriate frame
    viewToAnimate.tag = 1;
    [cell.contentView addSubview:viewToAnimate];

    return cell;
}

When you spawn your download process you pass in [cell.contentView viewWithTag:1]. When the download is done, it will update the appropriate view. If the table cell was reused the view will no longer have a superview and will not update the wrong cell.

There are things you can do to make this more efficient but this is the basic idea. If you have a custom UITableViewCell than this will probably look a bit different.


EDIT 2:

To reuse the viewToAnimate objects to make sure that they get updated if their parent cells were recycled, do something like the following:

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

    static NSString *MyIdentifier = @"MyTableCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                       reuseIdentifier:MyIdentifier] autorelease];
    } else {
        UIView *oldViewToAnimate = [cell.contentView viewWithTag:1];
        [oldViewToAnimate removeFromSuperview];
    }

    UIView *viewToAnimate = [self viewToAnimateForIndexPath:indexPath];
    viewToAnimate.tag = 1;
    [cell.contentView addSubview:viewToAnimate];

    return cell;
}

viewToAnimateForIndexPath will need to:

  • Check to see if a viewToAnimate has been created for this indexPath
  • Create a viewToAnimate if there isn't one
  • Save a reference to the view that can be looked up by indexPath
  • Return the viewToAnimate so the table cell can use it

I don't know enough about your data structure to do this for you. Once the download process completes it can call this same method to get the view and animate it.

MrHen
The problem is the UIView pointer you store might no longer be valid when you callback is done. That UIView might be used to represent another cell. Furthermore the actual cell that needs to be animated might now have another new UIVIew representing it.
erotsppa
The UIView should NOT represent the cell. It should represent the contents of the cell that may need to be animated. So the cellView has a subview of contentView. When cellView gets reused it should create a NEW contentView and release the old contentView.
MrHen
What do you mean? Did you read my message carefully? When you scroll the tableView, the UIViews pointers are not longer valid because they get shuffled around.
erotsppa
I edited the original comment. Hopefully that makes things clearer.
MrHen
Yes it does, it's getting there but not complete. What if the user scrolls up and the cell is reused for another row and then the user scrolls back down and the should-animate row is revealed again. The callback won't be able to animate it as it is now removed from the superview.
erotsppa
There are a few ways to do this but they depend on the details of how you are (a) looking for the picture object (b) spawning the download process and (c) saving the picture object when downloaded. The way I would do this to give the data object that is holding the pictures a method similar to [pictureDataObject viewForIndexPath:indexPath]. This pictureDataObject would keep a reference to viewToAnimate. Instead of creating a new viewToAnimate for each cell I would ask the pictureDataObject for the view appropriate to for indexPath. I can add this to the original comment if it would help.
MrHen
Can you please add to the original comment. It's quite hard to understand it from just the comment.
erotsppa
I added an update. This is about as far as I can go without looking at more of your code.
MrHen