Search code examples
iosuicollectionviewfocusios15

Is there a way to change the focused index path in a collection view?


I am building an app like Photos where you can scroll through thumbnails of your photos in a UICollectionView and you can tap on one to view that photo full-screen then swipe to move between photos. I'm working to add support for keyboard navigation so you can use the arrow keys to select a photo, hit space to view it full-screen, use the arrow keys to move between full-screen photos, then hit space to dismiss it. This works well in the Photos app, but in my app when you dismiss the full-screen view controller, the focus does not update in the underlying view controller to the index path of the photo you just dismissed - it's obviously only aware of the index path that was last focused in that view controller, the one focused prior to pressing space. It seems I need to manually move focus to a potentially different index path when the full screen view controller is dismissed. How do you accomplish that?

To enable focus I set these in the UICollectionViewController:

collectionView.allowsFocus = true
collectionView.allowsFocusDuringEditing = true
collectionView.remembersLastFocusedIndexPath = true
restoresFocusAfterTransition = true

I have tried the following but the focus is not moved to that cell, even if I set remembersLastFocusedIndexPath and restoresFocusAfterTransition to false:

cell.focusGroupPriority = .currentlyFocused
cell.setNeedsFocusUpdate()
cell.updateFocusIfNeeded()
cell.becomeFirstResponder()

Solution

  • It is possible to change the focused index path if there is already a focused index path.

    To do this, implement the collection view delegate method indexPathForPreferredFocusedView(in:) to return the index path you want to be focused. When you want to change the focus, call collectionView.setNeedsFocusUpdate() and that function will be called by the system giving you the opportunity to specify the index path to focus. Note iOS will now be asking your app to tell it which index path to focus initially and as focus states change. You can return nil to let the system decide which to focus.

    Note you must not set collectionView.remembersLastFocusedIndexPath to true otherwise this will not work. To have that functionality, you'll need to manually keep track of the last focused index path using collectionView(_:didUpdateFocusIn:with:) and return that in indexPathForPreferredFocusedView(in:).

    func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? {
        return lastFocusedIndexPath
    }
    
    func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        lastFocusedIndexPath = context.nextFocusedIndexPath
    }
    
    private func moveFocus(to indexPath: IndexPath) {
        lastFocusedIndexPath = indexPath
        collectionView.setNeedsFocusUpdate()
        //collectionView.updateFocusIfNeeded() //can update it now if you need it to
    }