Cannot move focus off of UIButton in UICollectionViewCell

I have a

UICollectionView
of custom
UICollectionViewCell
subclasses, each with a
UILabel
and a
UIButton
. I want to make use of the great default focus behavior of the
UIButton
(since
UICollectionViewCell
s don't have any default focus behavior), so I override the
preferredFocusedView
property of the
UICollectionViewCell
subclass to return the
UIButton
:


@IBOutlet weak var button:UIButton!
@IBOutlet weak var textLabel:UILabel!

override var preferredFocusedView:UIView?
{
     get {
          return self.button
     }
}

override func canBecomeFocused() -> Bool
{
     return true
}


When I do this, I'm unable to move the selection to the next

UICollectionViewCell
. The
context
object of
collectionView(_:didUpdateFocusInContext:withAnimationCoordinator:)
reports that the previous and next
NSIndexPath
s are always (0,0).


If I instead return

self
or
self.textLabel
from
preferredFocusView
and do custom focus appearance changes (like changing the
backgroundColor
of the
UICollectionViewCell
), I'm able to move focus around the
UICollectionView
without problem.
collectionView(_:canFocusItemAtIndexPath:)
has not been overridden. Is there some default behavior of focus in the
UIButton
that I'm overlooking?

Accepted Reply

try adding


  override func collectionView(collectionView: UICollectionView, canFocusItemAtIndexPath indexPath: NSIndexPath) -> Bool {
    return false
  }


in addition to not overridding preferredFocusedView

Replies

This is because when you're trying to focus the next cell it's going Button A -> Cell A -> Button A.


No need to return a preferred focus view for the cell, the focus engine will automatically find the focusable buttons.

If

preferredFocusedView
is not overridden to return the
UIButton
, the focus moves around the
UICollectionView
as expected, but the
UIButton
in the
UICollectionViewCell
does not receive focus (does not get the white background with shadow), as evidenced by firings of
collectionView(_:didUpdateFocusInContext:withAnimationCoordinator:)
. The
focused
property of the
UIButton
when moving through the
UICollectionView
(from
nextFocusedIndexPath
) in that delegate method remains
false
.

Remove the -canBecomeFocused method from your collection view cell and then they won't even be considered for focus.

The reason for overriding canBecomeFocused is because the docs say that the default value is false. Regardless, removing the overridden method doesn't change the behavior I'm seeing.


Here's a minimum working example. Reproduce yourself by (project posted separately to avoid moderation):


  1. New Single View project (Obj-C this time).
  2. Change ViewController to a UICollectionViewController.
  3. Swap out Main.storyboard's ViewController for a UICollectionViewController, changing class to ViewController.
  4. Add a new UICollectionViewController subclass.
  5. Populate a single UIButton in the prototype cell in the storyboard, changing the class (to that from step 4) and reuse ID, and adding an outlet to the subclass created in step 4.
  6. Populate required UICollectionViewDataSource methods in ViewController (numberOfItems..., and cellForItem...).
  7. Populate collectionView:didUpdateFocus... to observe focus changes.
  8. Run.


Without overriding preferredFocusView in TestCollectionViewCell, you can move around the UICollectionView fine, but the UIButton (the only view in the cell) never gets focus. If you return the button from preferredFocusedView, the button gets focus, but only in (0,0).

Here's the aformentioned example project, which won't be posted for a while due to moderation.

try adding


  override func collectionView(collectionView: UICollectionView, canFocusItemAtIndexPath indexPath: NSIndexPath) -> Bool {
    return false
  }


in addition to not overridding preferredFocusedView

This works! This also makes no sense on immediate inspection: the cell cannot obtain focus, but one of its child views can? This lead me to discover something else. UICollectionView has a property, remembersLastFocusedIndexPath, that I set to true. If this is false, I do not need to implement canFocusItemAtIndexPath. Reading the documentation a little more thoroughly, I think I see what may have been happening. A reading of the documentation for this property along with mwhuss's initial reply may reveal that this isn't a bug, but perhaps really strange default behavior (in my opinion).