Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force UITableView to Scroll to Tops of Cells

Is there a way to make UITableViews scroll cell to cell? That is, the top of the Table View is always the top of a UITableViewCell.

I tried the pageation flag, but that seems to scroll whole "pages." I just want cell to cell scrolling.

Thanks

like image 405
Tyler DeWitt Avatar asked Oct 17 '11 18:10

Tyler DeWitt


4 Answers

UTTableView is a subclass of UIScrollView, so you can use scrollViewDidScroll method to rearrange your TableView scroll position after scroll.

You can use tableView.contentOffset to get where is your current scroll position. And by dividing it to a meaningful number you can get which cell is on top.

int cellIndex = tableView.contentOffset / cellHegiht;

Or you can get currently visible cell on center (top, bottom) like this:

NSArray *indexPathsForVisibleRows = [tableView indexPathsForVisibleRows];
NSIndexPath *selectedIndexPath = [indexPathsForVisibleRows objectAtIndex:(indexPathsForVisibleRows.count / 2)]; //this gets center cell

After calculating which cell should be clicked by its edge on top (or bottom) you can correct the drift from cell edge by calling:

[tableView scrollToRowAtIndexPath:selectedIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];

You can use UIScrollViewDelegate methods to activate snapping. You can activate either when the scrolling animation completed or while the animation still continues. This will generate different user interaction and can't say one is better then another.

But just implementing following method and nothing else looks like going to be my favorite:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset {
    int cellHeight = 41;
    *targetContentOffset = CGPointMake(targetContentOffset->x,
                                       targetContentOffset->y - (((int)targetContentOffset->y) % cellHeight));
}

This method gets called when user touches up on TableView to notify application. targetContentOffset is an inout variable so you can actually set final scroll position while animation is continuing. Using right cellHeight, your TableView will always snap to cells.

like image 103
Töre Çağrı Uyar Avatar answered Oct 06 '22 07:10

Töre Çağrı Uyar


If you have sections, I found this the most reliable approach:

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate(BOOL)decelerate {
    if (decelerate == NO) {
        [self autoAdjustScrollToTop];
    }
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self autoAdjustScrollToTop];
} 
- (void)autoAdjustScrollToTop {
    // compare the top two visible rows to the current content offset
    // and auto scroll so that the best row is snapped to top
    NSArray *visibleRows = [self.tableView indexPathsForVisibleRows];
    NSIndexPath *firstPath = visibleRows[0];
    NSIndexPath *secondPath = visibleRows[1];
    CGRect firstRowRect = [self.tableView rectForRowAtIndexPath:firstPath];
    [self.tableView scrollToRowAtIndexPath:(firstRowRect.origin.y > self.tableView.contentOffset.y ? firstPath : secondPath) atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
like image 40
David James Avatar answered Oct 06 '22 09:10

David James


Although totally correct, I didn't find the answer by @randle very helpful. I read the developers documentation, and I was even more confused. How ever I finally discovered how simple the answer was, but sometimes we need a little but more help than "call this method". See under the comment // scroll to row! here:

- (BOOL)textFieldShouldReturn:(UITextField *)textField 
{
    if (textField == self.firstInput) {
        [self.secondInput becomeFirstResponder];

        // scroll to row!
        [self.tableView scrollToRowAtIndexPath: // use the method
             [NSIndexPath indexPathForRow:1     // get 2nd row (nth row...)
                                inSection:0]    // in 1st section (...)
                         // set position: you can use bottom, middle or top.
                         atScrollPosition:UITableViewScrollPositionBottom  
                                 animated:YES]; // YES or NO.

    } else if (textField == self.secondInput) {
        [textField resignFirstResponder];
    }
    return YES;
}

Now, this is for a static cell tableView. I know the amount of rows, I know which row I am going to. This could be expanded out to better code, and accessing variables programmatically, but it show HOW this code works in practice at a low level.

I hope this adds a layer of usefulness to anyone else searching the forum.

I did a complete write up of the method here: http://iosanswers.blogspot.com/2012/02/table-view-forms-scroll-to-selected.html

like image 42
Peter J. Nicholls Avatar answered Oct 06 '22 08:10

Peter J. Nicholls


This approach handles a table view with varying row heights. It assumes the table view has a single section.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset {

    // Find the row where the animation will currently end.
    NSInteger targetRow = [[self.tableView indexPathForRowAtPoint:*targetContentOffset] row];

    // Tell the animation to end at the top of that row.
    *targetContentOffset = [self offsetForTopOfRow:targetRow];
}

The offset is calculated by this helper method, which sums the heights of all rows above the target row.

- (CGPoint)offsetForTopOfRow:(NSInteger)row {

    CGPoint offset = CGPointZero;

    for (int i = 0; i < row; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        CGFloat height = [self.tableView.delegate tableView:self.tableView heightForRowAtIndexPath:indexPath];
        offset.y += height;
    }

    return offset;
}
like image 42
Jack Bransfield Avatar answered Oct 06 '22 09:10

Jack Bransfield