indexPathForPreferredFocusedView is not being Called
Asked Answered
M

6

9

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?

Mackenziemackerel answered 2/2, 2018 at 15:31 Comment(0)
K
8

You should set remembersLastFocusedIndexPath to true.

collectionView.remembersLastFocusedIndexPath = true
Koeppel answered 5/12, 2019 at 9:46 Comment(2)
Sounds like the opposite. The docs say that remembersLastFocusedIndexPath must be set to false for it to call.Humpage
I've tried both. With false - method not called. With true - work.Martines
R
5

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)
    }
}

collectionView

Recce answered 3/2, 2018 at 11:18 Comment(5)
I am doing the same thing, delegate is assigned but the method is not being called. how can I solve this problem if it is due to a focus problem ?Mackenziemackerel
@AymenHARRATH check the overriden property preferredFocusEnvironments in the example that i added.Recce
I have the sameMackenziemackerel
only for the first time, it is getting called. Then updating focus not calling this functionMacaulay
I run your code on ipad, preferredFocusEnvironments and indexPathForPreferredFocusedView method are not called, the middle cell is not focused, is this tvOS only?Fredette
A
2

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:

  1. Implement the delegate method:

    func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? {
    return IndexPath(item: 5, section: 0)
    }
    
  2. 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]
}
Alexandros answered 21/8, 2018 at 4:48 Comment(1)
No those two functions do not force a call on the indexPathForPreferredFocusedView Lapotin
N
0

I had this issue too.

To fix it:

  1. collectionView.remembersLastFocusedIndexPath = true

And after reloadData():

  1. collectionView.setNeedsFocusUpdate() collectionView.updateFocusIfNeeded()

To force it focus in last item focused.

Nativeborn answered 14/5, 2020 at 18:38 Comment(0)
W
-1

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.

Wireworm answered 4/8, 2018 at 18:2 Comment(2)
if the remembersLastFocusedIndexPath property is false, or if there is no saved index path because no cell was previously focused, the collection view calls this method so that you can specify which cell should receive focus. If you do not implement this method, the collection view returns an appropriate cell.- apple docs. But The method indexPathForPreferredFocusedView is not called unless the remembersLastFocusedIndexPath is set to true. Looks like you also experienced the same behaviour.Alexandros
Apple docs are right. It says “if you don’t not implement this method, the collection view returns an appropriate cell”. For the very first time when the collectionView gets focus, indexPathForPreferredFocusedView won’t be called unless we set remembersLastFocusedIndexPath to true. And when the collectionView is reloaded, indexPathForPreferredFocusedView will be called, irrespective of remembersLastFocusedIndexPath value.Wireworm
F
-1

Three things that solved the same problem I was facing.

  1. self.restoresFocusAfterTransition = false

  2.   func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> 
          IndexPath? {
              return IndexPath(item: 3, section: 0)
          }
    
  3.   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.

Frowsty answered 27/9, 2018 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.