With collectionView.isPagingEnabled = true how to know when paging is completed

2.9k views Asked by At

I have a vertical direction cell that is the same size of the collectionView and I set collectionView.isPagingEnabled = true. There is only 1 active visible cell on screen at a time:

layout.scrollDirection = .vertical
collectionView.isPagingEnabled = true

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    // collectionView is the same width and height as the device

    let width = collectionView.frame.width
    let height = collectionView.frame.height
    return CGSize(width: width, height: height)
}

I need to know when the entire cell is on screen after the user swipes either up and down (when the page is finished paging and centered on screen). Or in other words I need to know when the user isn't swiping or dragging in between cells. I tried this:

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    if decelerate {
            
        if scrollView.frame.height == collectionView.frame.height {
                
           let indexPaths = collectionView.indexPathsForVisibleItems
           for indexPath in indexPaths {

                if let cell = collectionView.cellForItem(at: indexPath) as? MyCell {
                    print("currentCell: \(indexPath.item)") // do something in cell
                }
            }
        }
    }
}

The problem I ran into is even if I drag 1/2 way, meaning if I'm on item 2, drag 1/2 to item 3, then drag back to item 2, the print("currentCell: \(indexPath.item)") will print out currentCell: 3 then currentCell: 2. The only time it should print currentCell: 3 is if that cell is completely on screen and not in between cell 2 and 3 (or 2 and 1 etc).

2

There are 2 answers

4
Gulfam Khan On BEST ANSWER

You can try something like this:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let y = targetContentOffset.pointee.y
    let item = Int(y / view.frame.height)
    print("current cell: \(item)")
}

In this code I am using frame's width to divide x as my collectionView was horizontal, you can use height for vertical axis. Hope it helps

10
nghiahoang On

you could use :

var currentCellIndexPath: IndexPath? = nil {
   didSet {
      guard oldValue != currentCellIndexPath else { return }
      // Get the cell
   }
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
     let ratio = scrollView.contentOffset.y / scrollView.contentSize.height
     let candidate = ratio.rounded()
     
     guard abs(candidate - ratio) < 0.1 else { return }
     
     guard collectionView.indexPathsForVisibleItems.count > 1 else {
         guard !collectionView.indexPathsForVisibleItems.isEmpty else {
             return
         }

         currentCellIndexPath = collectionView.indexPathsForVisibleItems[0]
     }

     let firstIdx = collectionView.indexPathsForVisibleItems[0]                    
     let secondIdx = collectionView.indexPathsForVisibleItems[1]

     if candidate > ratio {
        currentCellIndexPath = secondIdx > firstIdx ? secondIdx : firstIdx
     } else {
        currentCellIndexPath = secondIdx > firstIdx ? firstIdx : secondIdx
     }
        
}

Another solution, for your case, you have maximum 2 index paths in collectionView.indexPathsForVisibleItems.

so all you need is check what is correct one if there're two index paths. you can check by:

let ratio = scrollView.contentOffset.y / scrollView.contentSize.height
let isUpper = ratio - CGFloat(Int(ratio)) > 0.5

then if isUpper = true, it's the second indexPath in array.