Alk
Alk

Reputation: 5547

Check whether cell at indexPath is visible on screen UICollectionView

I have a CollectionView which displays images to the user. I download these in the background, and when the download is complete I call the following func to update the collectionViewCell and display the image.

func handlePhotoDownloadCompletion(notification : NSNotification) {
    let userInfo:Dictionary<String,String!> = notification.userInfo as! Dictionary<String,String!>
    let id = userInfo["id"]
    let index = users_cities.indexOf({$0.id == id})
    if index != nil {
        let indexPath = NSIndexPath(forRow: index!, inSection: 0)
        let cell = followedCollectionView.cellForItemAtIndexPath(indexPath) as! FeaturedCitiesCollectionViewCell
        if (users_cities[index!].image != nil) {
            cell.backgroundImageView.image = users_cities[index!].image!
        }
    }
}

This works great if the cell is currently visible on screen, however if it is not I get a fatal error: unexpectedly found nil while unwrapping an Optional value error on the following line :

 let cell = followedCollectionView.cellForItemAtIndexPath(indexPath) as! FeaturedCitiesCollectionViewCell

Now this function does not even need to be called if the collectionViewCell is not yet visible, because in this case the image will be set in the cellForItemAtIndexPath method anyway.

Hence my question, how can I alter this function to check whether the cell we are dealing with is currently visible or not. I know of the collectionView.visibleCells() however, I am not sure how to apply it here.

Upvotes: 16

Views: 25865

Answers (8)

Thush-Fdo
Thush-Fdo

Reputation: 514

For someone searching a solution for this in 2024, with Swift 5

Make an extension to your UICollectionView and add below,

extension UICollectionView {

    func isCellAtIndexPathFullyVisible(_ indexPath: IndexPath) -> Bool {

        guard let layoutAttribute = layoutAttributesForItem(at: indexPath) else {
            return false
        }

        let cellFrame = layoutAttribute.frame
        return self.bounds.contains(cellFrame)
    }

}

and the usage:

if collectionView.isCellAtIndexPathFullyVisible(indexPath) {
    print("cell is fully visible")
} else {      
    print("cell is not fully visible")
}

Upvotes: 0

nTom
nTom

Reputation: 1

You can manage visible cells through the following.

https://github.com/NohEunTae/VisibilityTrackableCollectionView

just set the boundary to detect:

collectionView.setBoundary(.init(view: someView, mode: .safeArea))

Upvotes: -1

Sheng Li
Sheng Li

Reputation: 117

Yeah, I tried many approach and found this was the best.

func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
   const currentIndex = (indexPath.row + 1) % (total cell number)
} 

Upvotes: -2

Rahul Serodia
Rahul Serodia

Reputation: 459

I needed to track cells that are visible so you can also do something like this to track your cells better.

private func configureVisibleIndexPath() {
        let visibleCells = collectionView.indexPathsForVisibleItems
        visibleCells.forEach { indexPath in
            if let cell = collectionView.cellForItem(at: indexPath), collectionView.bounds.contains(cell.frame) {
                print("visible row is \(indexPath.row)"
            }
        }
    }

Call this function when your collection view is configured properly and it also takes care on scrolling by calling it in scrollView delegates:

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        configureVisibleIndexPath()
    }
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    configureVisibleIndexPath()
}

Upvotes: 7

Abdul Karim Khan
Abdul Karim Khan

Reputation: 4935

You can get current index by

// Called before the cell is displayed    
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    print(indexPath.row)
}

// Called when the cell is displayed
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    print(indexPath.row)
}

Upvotes: 3

tuledev
tuledev

Reputation: 10317

Get current available cells

// get visible cells 
let visibleIndexPaths = followedCollectionView.indexPathsForVisibleItems

Then check if your indexPath is contained in the visibleIndexPaths array or not, before doing anything with cells.

Upvotes: 31

Scott Fister
Scott Fister

Reputation: 1283

You can simply use if collectionView.cellForItem(at: indexPath) == nil { }. The collectionView will only return a cell if it is visible.

Or in your case specifically change:

let cell = followedCollectionView.cellForItemAtIndexPath(indexPath) as! FeaturedCitiesCollectionViewCell

to:

if let cell = followedCollectionView.cellForItemAtIndexPath(indexPath) as? FeaturedCitiesCollectionViewCell { }

Upvotes: 2

Fran Pugl
Fran Pugl

Reputation: 484

Nested UICollectionViews need oft to not scroll at all, so that no contentOffset is ever provided and thus iOS understands all cells as being always visible. In that case it is possible to take the screen boundaries as reference:

    let cellRect = cell.contentView.convert(cell.contentView.bounds, to: UIScreen.main.coordinateSpace)
    if UIScreen.main.bounds.intersects(cellRect) {
        print("cell is visible")
    }

Upvotes: 11

Related Questions