Search code examples
swiftcocoa-touchuicollectionviewuicollectionviewlayout

How to detect if last cell is visible in UICollectionView?


I am trying to detect if the last cell in a collectionView is visible.

var isLastCellVisible: Bool {

    let lastIndexPath = NSIndexPath(forItem: self.messages.count - 1, inSection: 0)
    let visibleIndexPaths = self.collectionView.indexPathsForVisibleItems()

    let lastCellPositionY = self.collectionView.layoutAttributesForItemAtIndexPath(lastIndexPath)!.frame.origin.y
    let bottomInset = self.collectionView.contentInset.bottom // changes when the keyboard is shown / hidden
    let contentHeight = self.collectionView.contentSize.height

    if visibleIndexPaths.contains(lastIndexPath) && (contentHeight - lastCellPositionY) > bottomInset {
        return true
    } else {
        return false
    }
}

What works:

If the last cell is visible and the keyboard is shown so that the cell is not hidden by it, the above code returns true. If it is hidden it returns false.

But I can't figure out how to return true, when the user scrolls up and the last cell is above the keyboard.

lastCellPositionY and contentHeight don't change when the collectionView gets scrolled up. Instead self.collectionView.bounds.origin.y does change, but I don't know how to compare lastCellPositionY to it, since they don't share the same origin and self.collectionView.bounds.origin.y is significantly less than lastCellPositionY.


Solution

  • I use this code (translated to Swift from the commits in this thread: https://github.com/jessesquires/JSQMessagesViewController/issues/1458)

    var isLastCellVisible: Bool {
    
        if self.messages.isEmpty {
            return true
        }
    
        let lastIndexPath = NSIndexPath(forItem: self.messages.count - 1, inSection: 0)
        var cellFrame = self.collectionView.layoutAttributesForItemAtIndexPath(lastIndexPath)!.frame
    
        cellFrame.size.height = cellFrame.size.height
    
        var cellRect = self.collectionView.convertRect(cellFrame, toView: self.collectionView.superview)
    
        cellRect.origin.y = cellRect.origin.y - cellFrame.size.height - 100 
        // substract 100 to make the "visible" area of a cell bigger
    
        var visibleRect = CGRectMake(
            self.collectionView.bounds.origin.x,
            self.collectionView.bounds.origin.y,
            self.collectionView.bounds.size.width,
            self.collectionView.bounds.size.height - self.collectionView.contentInset.bottom
        )
    
        visibleRect = self.collectionView.convertRect(visibleRect, toView: self.collectionView.superview)
    
        if CGRectContainsRect(visibleRect, cellRect) {
            return true
        }
    
        return false
    }