Atul Bhatia Atul Bhatia - 6 days ago 11
iOS Question

ios uitableview fade bottom cell and top cell as you scroll

I am fading out cells of my uitableview as they scroll out of view, or fading them in as they scroll into view. The issue I am facing is that if I scroll really fast sometimes the cells that are completely visible remain dimmed. Here is my code below:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// Fades out top and bottom cells in table view as they leave the screen
NSArray *visibleCells = [self.tableView visibleCells];

if (visibleCells != nil && [visibleCells count] != 0) { // Don't do anything for empty table view

/* Get top and bottom cells */
UITableViewCell *topCell = [visibleCells objectAtIndex:0];
UITableViewCell *bottomCell = [visibleCells lastObject];

/* Make sure other cells stay opaque */
// Avoids issues with skipped method calls during rapid scrolling
for (UITableViewCell *cell in visibleCells) {
cell.contentView.alpha = 1.0;
}

/* Set necessary constants */
NSInteger cellHeight = topCell.frame.size.height - 1; // -1 To allow for typical separator line height
NSInteger tableViewTopPosition = self.tableView.frame.origin.y;
NSInteger tableViewBottomPosition = self.tableView.frame.origin.y + self.tableView.frame.size.height;

/* Get content offset to set opacity */
CGRect topCellPositionInTableView = [self.tableView rectForRowAtIndexPath:[self.tableView indexPathForCell:topCell]];
CGRect bottomCellPositionInTableView = [self.tableView rectForRowAtIndexPath:[self.tableView indexPathForCell:bottomCell]];
CGFloat topCellPosition = [self.tableView convertRect:topCellPositionInTableView toView:[self.tableView superview]].origin.y;
CGFloat bottomCellPosition = ([self.tableView convertRect:bottomCellPositionInTableView toView:[self.tableView superview]].origin.y + cellHeight);

/* Set opacity based on amount of cell that is outside of view */
CGFloat modifier = 1.2; /* Increases the speed of fading (1.0 for fully transparent when the cell is entirely off the screen,
2.0 for fully transparent when the cell is half off the screen, etc) */
CGFloat topCellOpacity = (1.0f - ((tableViewTopPosition - topCellPosition) / cellHeight) * modifier);
CGFloat bottomCellOpacity = (1.0f - ((bottomCellPosition - tableViewBottomPosition) / cellHeight) * modifier);

/* Set cell opacity */
if (topCell) {
topCell.alpha = topCellOpacity;
}
if (bottomCell) {
bottomCell.alpha = bottomCellOpacity;
}
}
}


Any idea as to why when I scroll really fast the cells in view sometimes remain dimmed?

Rob Rob
Answer

The issue is that the scroll event will not always happen frequently enough that a cell that scrolls into view will go from the faded alpha to an alpha of 1.0 as it scrolls from the top/bottom to the middle. But when you scroll really quickly, the scrollViewDidScroll isn't called frequently enough to ensure that this is the case. And your loop that apparently is attempting to reset the alpha is updating the alpha of the wrong view (contentView rather than the cell itself).

You can remedy this by replacing your existing for loop:

for (UITableViewCell *cell in visibleCells) {
    cell.contentView.alpha = 1.0;
}

With

for (UITableViewCell *cell in visibleCells) {
    cell.alpha = 1.0;
}

Or, alternatively, eliminate that loop from there and then replace the lines that set the alpha of the top and bottom cells:

if (topCell) {
    topCell.alpha = topCellOpacity;
}
if (bottomCell) {
    bottomCell.alpha = bottomCellOpacity;
}  

With:

for (UITableViewCell *cell in self.tableView.visibleCells) {
    if (cell == topCell) {
        cell.alpha = topCellOpacity;
    } else if (cell == bottomCell) {
        cell.alpha = bottomCellOpacity;
    } else {
        cell.alpha = 1.0;
    }
}

By the way, another way to achieve a similar effect is to apply a gradient mask to the whole tableview and retire the scrollViewDidScroll method. The only trick here is that you cannot apply the gradient mask to the table view, itself (or else the gradient will scroll with the table view), but rather put the tableview inside some container UIView, and then apply the mask to that:

- (void)viewDidAppear:(BOOL)animated
{
    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.containerView.bounds;
    gradient.colors = @[(id)[UIColor clearColor].CGColor,
                        (id)[UIColor whiteColor].CGColor,
                        (id)[UIColor whiteColor].CGColor,
                        (id)[UIColor clearColor].CGColor];
    gradient.locations = @[@0.0, @0.1, @0.9, @1.0];
    self.containerView.layer.mask = gradient;
}

This is admittedly a slightly different effect, but sometimes it's desirable. It just depends upon what you're shooting for.

Comments