20

enter image description here

From the diagram above I have UICollectionView with 4 custom cells. At any time 2 or three cells can be on the screen. How can I tell when "cell 1" or "cell 2" is 100% on the screen?

Both

collectionView.visibleCells
collectionView.indexPathsForVisibleItems

return arrays and doesn't tell you if what cell 100% on the screen.

In the case of the image, the following will be display on didSelectItemAt

collectionView.visibleCells

[<Shot_On_Goal.MainCollectionViewCell: 0x101f525c0; baseClass = UICollectionViewCell; frame = (190 7.66667; 454 350); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x1c0237300>>, <Shot_On_Goal.HeaderCollectionViewCell: 0x101f4d580; baseClass = UICollectionViewCell; frame = (10 0; 170 365); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x1c0236800>>, <Shot_On_Goal.TheirHockeyNetCollectionViewCell: 0x101f55520; baseClass = UICollectionViewCell; frame = (654 7.66667; 454 350); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x1c0238fe0>>]

collectionView.indexPathsForVisibleItems

[[0, 1], [0, 0], [0, 2]] 
Paul S.
  • 1,342
  • 4
  • 22
  • 43
  • Have you considered using [CGRectContainsRect](https://developer.apple.com/documentation/coregraphics/1454186-cgrectcontainsrect) ? – Wez Oct 19 '17 at 12:38
  • Looks like someone did this with a [tableViewCell](https://stackoverflow.com/questions/9831485/best-way-to-check-if-uitableviewcell-is-completely-visible) already. – Wez Oct 19 '17 at 12:40

3 Answers3

17

This will return an Array of IndexPaths for the fully visible cells:

func fullyVisibleCells(_ inCollectionView: UICollectionView) -> [IndexPath] {

    var returnCells = [IndexPath]()

    var vCells = inCollectionView.visibleCells
    vCells = vCells.filter({ cell -> Bool in
        let cellRect = inCollectionView.convert(cell.frame, to: inCollectionView.superview)
        return inCollectionView.frame.contains(cellRect) 
    })

    vCells.forEach({
        if let pth = inCollectionView.indexPath(for: $0) {
            returnCells.append(pth)
        }
    })

    return returnCells

}

@IBAction func test(_ sender: Any) {

    let visCells = fullyVisibleCells(self.collectionView)
    print(visCells)

}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Where should this be called? Pressing button all the times seems quite absurd to me. I mean is there any way I can know as soon as my scrolling stops. ref : https://stackoverflow.com/q/53055965/4306200 – Aakash Dave Oct 30 '18 at 23:53
  • Thanks for the solution – Mehdi Mirzaei Mar 26 '19 at 13:28
  • Why collectionView.superview and not just collectionView? – Luchi Parejo Alcazar Dec 09 '21 at 17:12
  • 1
    @LuchiParejoAlcazar - if you convert the cell frame to `collectionView`, the resulting `origin.x` will be relative to the collection view's content. So, if your cells are 100-pts wide, and you've scrolled so the left edge of the 10th cell is at the left edge of the view, `cellRect.origin.x` will be `900` (9 cells are off-screen)... using `collectionView.superview` converts it relative to the superview, so `cellRect.origin.x` will be `0`. Try it yourself... add some `print()` statements to see the results. – DonMag Dec 09 '21 at 18:01
  • Does not work in case of UICollectionViewCompositionalLayout. returns random values in array – Faizyy May 04 '22 at 18:32
3

Here's an extension for it, based don DonMag's answer:

extension UICollectionView {
    var fullyVisibleCells : [UICollectionViewCell] {
        return self.visibleCells.filter { cell in
            let cellRect = self.convert(cell.frame, to: self.superview)
            return self.frame.contains(cellRect) }
    }
}
Lio
  • 4,225
  • 4
  • 33
  • 40
1

You can filter your visibleCells array to check if the frame of your cell is included in the frame of your collectionView:

    var visibleCells = self.collectionView?.visibleCells
    visibleCells = visibleCells?.filter({ cell -> Bool in
        return self.collectionView?.frame.contains(cell.frame) ?? false
    })
    print (visibleCells)
Y.Bonafons
  • 2,329
  • 13
  • 20