I need to specify which cell should receive focus.
According to Apple Documentation the indexPathForPreferredFocusedView
delegate method should be called if the remembersLastFocusedIndexPath
property is false
, or if there is no saved index path because no cell was previously focused.
In my case I am using a collection view in a UIViewController
and setting remembersLastFocusedIndexPath
to false
but indexPathForPreferredFocusedView
is not being called.
How explain this behaviour?
The function indexPathForPreferredFocusedView
is part of UICollectionViewDelegate, so it might be that the delegate was not assigned to your collectionView.
Or, the problem might also be on the focus environment, not taking into account your UICollectionView.
As a reference, here you have an example of a simple collectionView with 5 cells, having the one in the center initially selected by default
import UIKit
class ViewController: UIViewController {
private var collectionView: UICollectionView!
private var items = ["One", "Two", "Three", "Four", "Five"]
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView]
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.remembersLastFocusedIndexPath = false
MyCell.register(in: collectionView)
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCell.reuseIdentifier, for: indexPath) as! MyCell
cell.titleLabel.text = items[indexPath.row]
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? {
return IndexPath(row: 2, section: 0)
}
}
class MyCell: UICollectionViewCell {
static var reuseIdentifier: String { return String(describing: self) + "ReuseIdentifier" }
var titleLabel: UILabel!
public static func register(in collectionView: UICollectionView) {
collectionView.register(MyCell.self, forCellWithReuseIdentifier: MyCell.reuseIdentifier)
}
override init(frame: CGRect) {
super.init(frame: frame)
titleLabel = UILabel(frame: bounds)
backgroundColor = .blue
contentView.addSubview(titleLabel)
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
backgroundColor = isFocused ? .red : .blue
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Swift 4: I had the same issue. I wanted to reload collectionview
and force focus on a particluar cell. If I set collectionView.remembersLastFocusedIndexPath = true
, indexPathForPreferredFocusedView
delegate method is called. But it will remember the last focused indexPath as the property name says. This is not what I wanted. Apple docs are confusing.
Try this:
Implement the delegate method:
func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? {
return IndexPath(item: 5, section: 0)
}
Force update the focus [ex: in a button action]:
collectionView.setNeedsFocusUpdate()
collectionView.updateFocusIfNeeded()
This will force the collectionView to call the indexPathForPreferredFocusedView
method. We don't have to set the remembersLastFocusedIndexPath
to true.
NB: I had multiple collectionViews in my screen and the above steps
will work only if the currently focused collectionView and the
collectionView which we are forcing the focus update are same.
Use the preferredFocusEnvironments
to return the preferred collectionview to be in focus when the view loads [for example].
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView2]
}
From what I have tried, indexPathForPreferredFocusedView
is called only when the collectionView.remembersLastFocusedIndexPath
is set to true.
Also, when the collectionView
is reloaded again to get the preferred Focus.
Using preferredFocusEnvironments
is also a good solution, but it has its own cons.
If your ViewController
is only having collectionView
, then it is good to be used. If there are multiple focus items within the ViewController
, the focus behaviour will be different.
Three things that solved the same problem I was facing.
self.restoreFocusAfterTransition = false
func indexPathForPreferredFocusedView(in collectionView: UICollectionView) ->
IndexPath? {
return IndexPath(item: 3, section: 0)
}
override var preferredFocusEnvironments : [UIFocusEnvironment] {
//return collectionView in order for indexPathForPreferredFocusedView method to be called.
return [collectionView]
}
I had two collection view in two different view controllers. I had subclassed the UICollectionView and was performing a transition from one view controller to another.