I am displaying fairly large images in a UITableView
. As the user scrolls, I'd like to the table view to always snap the center-most photo in the middle. That is, when the table is in a resting state, it will always have a UITableViewCell
snapped to the center.
How does one do this?
You can use the UIScrollViewDelegate
methods on UITableView
to do this:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// if decelerating, let scrollViewDidEndDecelerating: handle it
if (decelerate == NO) {
[self centerTable];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self centerTable];
}
- (void)centerTable {
NSIndexPath *pathForCenterCell = [self.tableView indexPathForRowAtPoint:CGPointMake(CGRectGetMidX(self.tableView.bounds), CGRectGetMidY(self.tableView.bounds))];
[self.tableView scrollToRowAtIndexPath:pathForCenterCell atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
There is a UIScrollView delegate method especially for this!
The table view (which is a scroll view) will call - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
when the user stops scrolling. You can set manipulate the targetContentOffset
to ensure it ends up where you want, and it will decelerate ending at that position (just like a paging UIScrollView).
For example, if your cells were all 100 points high, you could do:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
targetContentOffset->y = 100 * (int)targetContentOffset->y/100;
}
Of course, you can also inspect the targetContentOffset
passed in to see where it was going to land, and then find the cell that is in and alter it appropriately.
Extending @jesse-rusak's answer above, this is the code you would need to add to your UITableViewController subclass if you have cells with variable heights. This will avoid the double-scroll issue in the accepted answer.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSIndexPath *pathForTargetTopCell = [self.tableView indexPathForRowAtPoint:CGPointMake(CGRectGetMidX(self.tableView.bounds), targetContentOffset->y)];
targetContentOffset->y = [self.tableView rectForRowAtIndexPath:pathForTargetTopCell].origin.y;
}
Building from what @jszumski posted, if you want the snap to occur mid drag, use this code:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
[self centerTable];
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
[self centerTable];
}
- (void)centerTable {
NSIndexPath *pathForCenterCell = [self.tableView indexPathForRowAtPoint:CGPointMake(CGRectGetMidX(self.tableView.bounds), CGRectGetMidY(self.tableView.bounds))];
[self.tableView scrollToRowAtIndexPath:pathForCenterCell atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
for the swift peeps
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
{
if decelerate == false
{
self.centerTable()
}
}
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.centerTable()
}
func centerTable()
{
let midX:CGFloat = self.tableView.bounds.midX
let midY:CGFloat = self.tableView.bounds.midY
let midPoint:CGPoint = CGPoint(x: midX, y: midY)
if let pathForCenterCell:IndexPath = self.tableView .indexPathForRow(at: midPoint)
{
self.tableView.scrollToRow(at: pathForCenterCell, at: .middle, animated: true)
}
}//eom
Extending @mikepj answer, (which in turn extended the great answer by @JesseRusak), this code lets you snap to a cell, even when cells have a variable (or unknown) height, and will snap to the next row if you'll scroll over the bottom half of the row, making it more "natural".
Original Swift 4.2 code: (for convenience, this is the actual code I developed and tested)
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard var scrollingToIP = table.indexPathForRow(at: CGPoint(x: 0, y: targetContentOffset.pointee.y)) else {
return
}
var scrollingToRect = table.rectForRow(at: scrollingToIP)
let roundingRow = Int(((targetContentOffset.pointee.y - scrollingToRect.origin.y) / scrollingToRect.size.height).rounded())
scrollingToIP.row += roundingRow
scrollingToRect = table.rectForRow(at: scrollingToIP)
targetContentOffset.pointee.y = scrollingToRect.origin.y
}
(translated) Objective-C code: (since this question is tagged objective-c
)
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSIndexPath *scrollingToIP = [self.tableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
if (scrollingToIP == nil)
return;
CGRect scrollingToRect = [table rectForRowAtIndexPath:scrollingToIP];
NSInteger roundingRow = (NSInteger)(round(targetContentOffset->y - scrollingToRect.origin.y) / scrollingToRect.size.height));
scrollingToIP.row += roundingRow;
scrollingToRect = [table rectForRowAtIndexPath:scrollingToIP];
targetContentOffset->y = scrollingToRect.origin.y;
}
UITableView extends UIScrollView...
myTableView.pagingEnabled = YES