3

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).

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256

2 Answers2

2

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

Gulfam Khan
  • 1,010
  • 12
  • 23
  • this works :)! I changed where you had an **x** to a **y**, changed **view.frame.width** to **view.frame.height**, removed **self.pagenumber** and put a print statement there instead. This way the next user can just c+p your code. Thanks for the help! Also, it gave me the exact index every time I swiped up and down, there was no need to use **-1**. Why do you think that is? – Lance Samaria Jun 24 '20 at 14:45
  • @LanceSamaria I said I am not sure about that, anyway I'll remove the statement to save others from misunderstanding, thanks for correction btw – Gulfam Khan Jun 24 '20 at 20:51
  • 1
    Thanks for the answer, this helped me tremendously, and only 3 lines of code. I added the upvote too Cheers !!! – Lance Samaria Jun 24 '20 at 23:42
  • happy to help :) – Gulfam Khan Jun 25 '20 at 09:50
0

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.

nghiahoang
  • 538
  • 4
  • 10
  • ok, 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. ` – nghiahoang Jun 24 '20 at 07:26
  • added another solution in the answer – nghiahoang Jun 24 '20 at 07:28
  • Where isUpper getting the index paths that are in the array from? I don't see where to use **let indexPaths = collectionView.indexPathsForVisibleItems; for indexPath in indexPaths { ... }** – Lance Samaria Jun 24 '20 at 07:34
  • because your collectionView displays only one cell at a time, so collectionView.indexPathsForVisibleItems has max two indexPaths. So if isUpper = true, the indexPath you want to use is collectionView.indexPathsForVisibleItems.last, and if isUpper = false, it'll be collectionView.indexPathsForVisibleItems.first – nghiahoang Jun 24 '20 at 07:38
  • I just tried, doesn't work. The first problem is when the cv calls reloadData **scrollViewDidScroll** doesn't get called so the code inside never runs and the second problem is the code is producing the incorrect indexPath. When I'm on cell 0, drag 1/2 way to to cell 1, the drag back to cell 0, the code never prints anything under the isUpper case, only the else (lower). Thanks for trying though :) – Lance Samaria Jun 24 '20 at 08:01
  • you don't get my point, just updated the answer, please try again – nghiahoang Jun 24 '20 at 08:08
  • still not working, maybe I wasn't clear with the questions. I need to know what cell is the current cell after the page snaps in place. When it's scrolling, the one that was snapped in place is the current cell until the next cell snaps in place. If dragging to the next cell from the current cell stops, and then goes back to the current cell, it will still be the cell the was initially snapped in place – Lance Samaria Jun 24 '20 at 08:31
  • In your didSet clause it's printing out both cell items while dragging, it shouldn't print anything until only 1 cell is snapped in place on screen – Lance Samaria Jun 24 '20 at 08:42
  • I see, to fix it you can change my implementation from scrollViewDidScroll to scrollViewDidEndDragging – nghiahoang Jun 24 '20 at 08:43
  • Just moved the code to scrollViewDidEndDragging, added it there and then added in there tried putting it inside decelerate {...} but it only was giving the values of 0 and 1 even as I scrolled down to the 3rd cell and then back and forth between the the 2nd and 4th. Much appreciated though :) – Lance Samaria Jun 24 '20 at 08:51