Electro-Bunny
Electro-Bunny

Reputation: 1396

Detecting closest collectionviewcell to the center of the collectionview

Suspect I am doing something fundamentally wrong below... I have a horizontal collectionview and after dragging I want to snap the closest cell to the center. But my results are unpredictable... what am I doing wrong here?

    func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    // Find collectionview cell nearest to the center of collectionView
    // Arbitrarily start with the last cell (as a default)
    var closestCell : UICollectionViewCell = collectionView.visibleCells()[0];
    for cell in collectionView!.visibleCells() as [UICollectionViewCell] {
        let closestCellDelta = abs(closestCell.center.x - collectionView.bounds.size.width/2.0)
        let cellDelta = abs(cell.center.x - collectionView.bounds.size.width/2.0)
        if (cellDelta < closestCellDelta){
            closestCell = cell
        }
    }
    let indexPath = collectionView.indexPathForCell(closestCell)
    collectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)
}

Upvotes: 8

Views: 5916

Answers (7)

moni
moni

Reputation: 1

 public func closestIndexPathToCenter() -> IndexPath? {
    guard let cv = collectionView else { return nil }
   
    let visibleCenterPositionOfScrollView = Float(cv.contentOffset.x + (cv.bounds.size.width / 2))
        var closestCellIndex = -1
        var closestDistance: Float = .greatestFiniteMagnitude
        for i in 0..<cv.visibleCells.count {
            let cell = cv.visibleCells[i]
            let cellWidth = cell.bounds.size.width
            let cellCenter = Float(cell.frame.origin.x + cellWidth / 2)

            // Now calculate closest cell
            let distance: Float = fabsf(visibleCenterPositionOfScrollView - cellCenter)
            if distance < closestDistance {
                closestDistance = distance
                closestCellIndex = cv.indexPath(for: cell)!.row
            }
        }
        if closestCellIndex != -1 {
            return IndexPath(row: closestCellIndex, section: 0)
        }else {
            return nil
        }
}

Upvotes: 0

vava044
vava044

Reputation: 61

var centerUICollectionViewCell: UICollectionViewCell? {
  get {
    // 1 
    guard let center = collectionView.superview?.convert(collectionView.center, to: collectionView) else { return nil }

    // 2  
    guard let centerIndexPath = collectionView.indexPathForItem(at: center) else { return nil }

    // 3
    return collectionView.cellForItem(at: centerIndexPath)
  }
}

1: Need to make sure we are converting the point from superview coordinate space to collectionView's space

2: Use the collectionView's local space point to find the indexPath

3: Return the cell for indexPath

Upvotes: 0

zumzum
zumzum

Reputation: 20238

None of the solutions reliably worked for me, so I wrote this and it works 100% of the time. Very simple. Even the built in indexPathForItemAtPoint was not working 100%.

extension UICollectionView {
    
    var centerMostCell:UICollectionViewCell? {
        guard let superview = superview else { return nil }
        let centerInWindow = superview.convert(center, to: nil)
        guard visibleCells.count > 0 else { return nil }
        var closestCell:UICollectionViewCell?
        for cell in visibleCells {
            guard let sv = cell.superview else { continue }
            let cellFrameInWindow = sv.convert(cell.frame, to: nil)
            if cellFrameInWindow.contains(centerInWindow) {
                closestCell = cell
                break
            }
        }
        return closestCell
    }
    
}

Upvotes: 1

Lucas
Lucas

Reputation: 1126

  • When stop dragging, just pick the center and use it to choose the indexpath

swift 4.2

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    let indexPath = photoCollectionView.indexPathForItem(at: photoCollectionView.center)
    photoCollectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: .centeredHorizontally, animated: true)
}

Upvotes: 0

Mike Carpenter
Mike Carpenter

Reputation: 410

Updated version for Swift 4

var closestCell = collectionView.visibleCells[0]
for cell in collectionView.visibleCells {
    let closestCellDelta = abs(closestCell.center.x - collectionView.bounds.size.width/2.0 - collectionView.contentOffset.x)
    let cellDelta = abs(cell.center.x - collectionView.bounds.size.width/2.0 - collectionView.contentOffset.x)
    if (cellDelta < closestCellDelta){
        closestCell = cell
    }
}

let indexPath = collectionView.indexPath(for: closestCell)
collectionView.scrollToItem(at: indexPath!, at: .centeredHorizontally, animated: true)

Upvotes: 0

Electro-Bunny
Electro-Bunny

Reputation: 1396

Turns out my original code was missing accounting for the collecitonview content offset. In addition, I've moved the centering into the scrollViewDidEndDecelerating callback. I've modified the original code and included it below.

    func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    // Find collectionview cell nearest to the center of collectionView
    // Arbitrarily start with the last cell (as a default)
    var closestCell : UICollectionViewCell = collectionView.visibleCells()[0];
    for cell in collectionView!.visibleCells() as [UICollectionViewCell] {
        let closestCellDelta = abs(closestCell.center.x - collectionView.bounds.size.width/2.0 - collectionView.contentOffset.x)
        let cellDelta = abs(cell.center.x - collectionView.bounds.size.width/2.0 - collectionView.contentOffset.x)
        if (cellDelta < closestCellDelta){
            closestCell = cell
        }
    }
    let indexPath = collectionView.indexPathForCell(closestCell)
    collectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)
}

Upvotes: 15

Ishan Handa
Ishan Handa

Reputation: 2281

Try this:

NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:self.collectionView.center];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];

SWIFT:

let indexPath = self.collectionView.indexPathForItemAtPoint(self.collectionView.center)
self.collectionView.scrollToItemAtIndexPath(indexPath!, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)

Upvotes: 2

Related Questions