Search code examples
iosswiftuicollectionviewuicollectionviewlayoutmac-catalyst

Find the actual displayed number of items per row in a UICollectionView


I have a UICollectionView of rectangular cells with mostly uniform size laid out with a stock UICollectionViewFlowLayout.

I am trying to implement keyboard arrow navigation in the Catalyst version of the App. Left and right are no problem. Here is a fully laid out example:

@objc func myRightKey() {
    var curr = glyphCollectionOutlet.indexPathsForSelectedItems?.first
    if curr == nil {curr = IndexPath(row: 0, section: 0)}
    if self.selectedCell == nil {self.selectedCell = curr} else {curr = self.selectedCell}
    let inSection = (curr!.section)
    // increment item in section up to max number of items
    let nextItem = curr!.item + 1
    let maxItem = glyphCollectionOutlet.numberOfItems(inSection: inSection) - 1
    let newItem = min(nextItem, maxItem)
    let newSelectPath = IndexPath(item: newItem, section: inSection)
    self.selectedCell = newSelectPath
    glyphCollectionOutlet.selectItem(at: newSelectPath, animated: true, scrollPosition: .centeredVertically)
    glyphCollectionOutlet.reloadData()
}

The problem here is with up and down navigation which should place the selected cell directly above or below the starting point. In Mac Catalyst, the viewController with the embedded collectionView can change width and thus change the number of cell/items in a row. Unfortunately, in a collectionView, the IndexPath.row is simply identical it seems to IndexPath.item, not at all related to displayed row.

I need to calculate the IndexPath of the next item which should be N items away from the current selection. It is true I can measure the current collectionView width, but the intercell spacing would still be variable. Is there a way to dynamically calculate or detect the number of items displayed in a row so I can calculate N?


Solution

  • There might be a way to do that, but I believe there is an even better approach.

    Why not take the center of the current cell, offset its y with the item height, and then check which index path is located there?

    let currentCenter = cell.center
    // Let's suppose you want to go downwards, just change to subtraction for going upwards
    let targetCenter = CGPoint(currentCenter.x, currentCenter.y + cell.bounds.height) // Or however you want to consider the item height
    let targetIndexPath = collectionView.indexPathForItemAtPoint(targetCenter)
    

    Also, this should work quite well for non-uniform item sizes as well.