xinatanil
xinatanil

Reputation: 634

tvOS UICollectionView get currently focused item's index path

So this is kind of a noobish question but I just can't figure out a very simple way to detect currently focused item's indexPath.
I looked around hoping to see something very easy like collectionView.indexPathOfCurrentlyFocusedItem but didn't find anything remotely close. So I digged around and tried to find something similar at UIFocusEnvironment, UIFocusUpdateContext trying to find the desired property but failed. So, the only solution I can come up with is just iterating through all visible cells and finding a cell with focused property set to true.

So is there a more simple and elegant way to find the currently focused item's indexPath? (Except tracking it through delegate method and saving it in view controller's property)

Upvotes: 10

Views: 11642

Answers (5)

Pranav Kasetti
Pranav Kasetti

Reputation: 9935

Focus for collection / table views

As outlined in the other answers, we only really need the currently focused items as part of delegate methods like collectionView.shouldUpdateFocusIn and collectionView.didUpdateFocusIn for layout purposes. I would advise using these methods if we need to customise the UI depending on focus for collection and table views.

Focus for general views

Use UIFocusItem.didUpdateFocus, UIFocusItem.shouldUpdateFocus outside of collection or table views. These methods are called for any view that is in the view hierarchy as the collection or table view being focused next or focused previously.

override func didUpdateFocus(
    in context: UIFocusUpdateContext,
    with coordinator: UIFocusAnimationCoordinator
) { }

override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool { }

Focus debugging

If we're using the LLDB debugger:

$ po UIFocusDebugger.status()

returns the currently focused item.

More useful focus debugging tools can be found here. Outside of focused or unfocused UI state styling, UIFocusDebugger is really handy.

Note

UIScreen.main.focusedView and UIScreen.main.focusedItem are now deprecated, so I don't advise using these since they may be removed from the API soon. Apple prefers to use the delegate methods for managing focus updates.

Upvotes: 0

Antoine
Antoine

Reputation: 23996

You can use UIScreen property focusedView as followed for this:

if let focusedCell = UIScreen.main.focusedView as? UICollectionViewCell {
    if let indexPath = collectionView.indexPath(for: focusedCell) {
        print("IndexPath is \(indexPath)")
    }
}

Upvotes: 20

Andrew Andreev
Andrew Andreev

Reputation: 41

So, your target is to do something when you get and lose focus particularly on a cell under tvOS. The catch is you're moving around other UI elements, and therefore the context could be different. You have to change in this context only those UIs that you have to care of.

The right place to make your implementation is func didUpdateFocusInContext(), like this:

override func didUpdateFocusInContext(
  context:                              UIFocusUpdateContext,
  withAnimationCoordinator coordinator: UIFocusAnimationCoordinator
) {
  coordinator.addCoordinatedAnimations({
    if let cell = context.previouslyFocusedView as? UICollectionViewCell {
      cell.layer.borderWidth = 2
    }

    if let cell = context.nextFocusedView as? UICollectionViewCell {
      cell.layer.borderWidth = 5
    }
  },
  completion: nil)
}

Now we're using the focus coordinator to apply our logic:

  • When the previously focused item is UICollectionViewCell then you have to release the focus to the next item. You shouldn't care what is the next item because it could be a collection cell or not. For fun, in this case, let's change the border to 2. This value could be set by default.
  • When the next focused item is UICollectionViewCell then you've to handle it is a similar way, or it will become a mess... So, let's change the border to 5.

As you can see, didUpdateFocusInContext() provides a generic approach for all views within your current visual context. You can apply the same approach for other UI elements.

Have a fun with tvOS...

Upvotes: 4

Jacksonkr
Jacksonkr

Reputation: 32247

Here's how I accomplished this in shouldUpdateFocusInContext

Solution

override func shouldUpdateFocusInContext(context: UIFocusUpdateContext) -> Bool {

    // The magic is in the next two lines
    let cell: UICollectionViewCell = context.nextFocusedView as! UICollectionViewCell
    let indexPath: NSIndexPath? = self.collectionView.indexPathForCell(cell)

    print(indexPath) 
    // <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}
    
    return true
}

Upvotes: 2

Jeroen Bakker
Jeroen Bakker

Reputation: 2169

Use didUpdateFocusInContect - UICollectionViewDelegate

func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
    if collectionView == self.collectionView {
        print(context.nextFocusedIndexPath)
    }
}

This wil return the indexPath of the cell that is going to be focused, you could also try:

context.previouslyFocusedIndexPath

Depends what you're trying to do.

Upvotes: 9

Related Questions