Add snap-to position in a UITableView or UIScrollView

14,515

Solution 1

Like Pavan stated, scrollViewWillEndDragging: withVelocity: targetContentOffset: is the method you should use. It works with table views and scroll views. The code below should work for you if you are using a table view or vertically scrolling scroll view. 44.0 is the height of the table cells in the sample code so you will need to adjust that value to the height of your cells. If used for a horizontally scrolling scroll view, swap the y's for x's and change the 44.0 to the width of the individual divisions in the scroll view.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    // Determine which table cell the scrolling will stop on.
    CGFloat cellHeight = 44.0f;
    NSInteger cellIndex = floor(targetContentOffset->y / cellHeight);

    // Round to the next cell if the scrolling will stop over halfway to the next cell.
    if ((targetContentOffset->y - (floor(targetContentOffset->y / cellHeight) * cellHeight)) > cellHeight) {
        cellIndex++;
    }

    // Adjust stopping point to exact beginning of cell.
    targetContentOffset->y = cellIndex * cellHeight;
}

Solution 2

I urge you to use the scrollViewWillEndDragging: withVelocity: targetContentOffset: method instead which is meant to be used for exactly your purpose. to set the target content offset to your desired position.

I also suggest you look at the duplicate questions already posted on SO.

Take a look at these posts

Solution 3

If you really must do this manually, here is a Swift3 version. However, it's highly recommended to just turn on paging for the UITableView and this is handled for you already.

let cellHeight = 139
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee.y = round(targetContentOffset.pointee.y / cellHeight) * cellHeight
}

// Or simply
self.tableView.isPagingEnabled = true

Solution 4

In Swift 2.0 when the table has a content inset and simplifying things, ninefifteen's great answer becomes:

func scrollViewWillEndDragging(scrollView:            UIScrollView,
                               withVelocity velocity: CGPoint,
                               targetContentOffset:   UnsafeMutablePointer<CGPoint>)
{
    let cellHeight = 44
    let y          = targetContentOffset.memory.y + scrollView.contentInset.top + cellHeight / 2
    var cellIndex  = floor(y / cellHeight)

    targetContentOffset.memory.y = cellIndex * cellHeight - scrollView.contentInset.top;
}

By just adding cellHeight / 2, ninefifteen's if-statement to increment the index is no longer needed.

Solution 5

Here's the Swift 2.0 equivalent

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let cellWidth = CGRectGetWidth(frame) / 7   // 7 days
    var index = round(targetContentOffset.memory.x / cellWidth)
    targetContentOffset.memory.x = index * cellWidth
}

And this complicated rounding isn't necessary at all as long as you use round instead of floor

Share:
14,515
Milo
Author by

Milo

Updated on June 17, 2022

Comments

  • Milo
    Milo almost 2 years

    Is it possible to add a snap-to position in a UITableView or UIScrollView? What I mean is not auto scroll to a position if I press a button or call some method to do it, I mean is if I scroll my scroll view or tableview around a specific point, say 0, 30, it will auto-snap to it and stay there? So if my scroll view or table view scrolls and then the user lets go inbetween 0, 25 or 0, 35, it will auto "snap" and scroll there? I can imagine maybe putting in an if-statement to test if the position falls in that area in either the scrollViewDidEndDragging:WillDecelerate: or scrollViewWillBeginDecelerating: methods of UIScrollView but I'm unsure how to implement this in the case of a UITableView. Any guidance would be much appreciated.

  • Pavan
    Pavan over 10 years
    Cheers bro, I did try telling him, a few times. But I think he's persistent in his own approach.
  • Dave Y
    Dave Y almost 4 years
    Perfect! Worked as a drop-in with minimal changes. Appreciate it.