Madu
Madu

Reputation: 5019

tvos UICollectionView lose focus to previous Focus Cell

I am building a tvos application. i have a strange bug where UICollectionView lose focus of the previously selected cell when i navigate back to that particular view. The scenario is some thing this like this.

I have two UIViewControllers A and B. A has a UITableView and it has three prototype cells in it. Each cell has a horizontal scrolling UICollectionView inside it. When i click on any of UICollectionViewCell it navigates to the B (detail page). I am presenting B modally.

Now when i press Menu button on Siri remote view A appears again (in other words view B is removed from View hierarchy) but the current selected cell is different then the previously selected. I have tried to use remembersLastFocusedIndexPath with both true and false values and also tried by implementing

func indexPathForPreferredFocusedViewInCollectionView(collectionView: UICollectionView) -> NSIndexPath?

but the control neves comes to this function when i navigate back to view A. I am also reloading every thing in viewWillAppear function.

Can any one help me in this. Thanks

Upvotes: 3

Views: 5058

Answers (3)

Anand Nanavaty
Anand Nanavaty

Reputation: 564

In Swift

If you want to focus collection view Cell then You can use collectionview Delegate method, method name is

func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) 
{
}

You can use this method like this...

func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {

if let previousIndexPath = context.previouslyFocusedIndexPath,
    let cell = collectionView.cellForItemAtIndexPath(previousIndexPath) {
    cell.contentView.layer.borderWidth = 0.0
    cell.contentView.layer.shadowRadius = 0.0
    cell.contentView.layer.shadowOpacity = 0
}

if let indexPath = context.nextFocusedIndexPath,
    let cell = collectionView.cellForItemAtIndexPath(indexPath) {
    cell.contentView.layer.borderWidth = 8.0
    cell.contentView.layer.borderColor = UIColor.blackColor().CGColor
    cell.contentView.layer.shadowColor = UIColor.blackColor().CGColor
    cell.contentView.layer.shadowRadius = 10.0
    cell.contentView.layer.shadowOpacity = 0.9
    cell.contentView.layer.shadowOffset = CGSize(width: 0, height: 0)
    collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: [.CenteredHorizontally, .CenteredVertically], animated: true)
}
} 

Upvotes: 0

Zarif Ahmed
Zarif Ahmed

Reputation: 361

The property remembersLastFocusedIndexPath should be set to true for the collectionView and false for the tableView.

Also, Are you reloading the UITableView in viewWillAppear i.e Is the table data being refreshed when the B is popped and A appears? If Yes, then lastFocusedIndexPath will be nil on reload.

We faced the same issue. We solved it by not reloading the contents when B is popped.

Maintain a flag say didPush. Set this flag to true when B is pushed. When A appears check whether the flag is set and only then fetch data and reload table.

This worked for us.

Upvotes: 3

Yoseob Lee
Yoseob Lee

Reputation: 197

I don't remember exactly, but I know there was a known issue for remembersLastFocusedIndexPath where it wasn't working as intended.

This is one workaround, although take it with a grand of salt as it does seem slightly hacky and it uses the common (but potentially unstable) approach of overriding the preferredFocusedView property.

private var viewToFocus: UIView?

override var preferredFocusView: UIView? {
    get {
        return self.viewToFocus
        }
    }

Save locally the indexPath of the last cell in View A when presenting View B

// [1] Saving and scrolling to the correct indexPath:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    ...
    collectionView.scrollToItemAtIndexPath(indexPath:, atScrollPosition:, animated:)
    }

// [2] Create a dispatchTime using GCD before setting the cell at indexPath as the preferredFocusView:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    ...
    let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(CGFloat.min * CGFloat(NSEC_PER_SEC)))
        dispatch_after(dispatchTime, dispatch_get_main_queue(), {
        self.viewToFocus = collectionView.cellForItemAtIndexPath(indexPath:)

        // [3] Request an update
        self.setNeedsFocusUpdate()

        // [4] Force a focus update
        self.updateFocusIfNeeded()
        }
    }

The reason we split the two methods into both viewWillAppear and viewDidAppear is that it eliminates a bit of the animation jump. If anyone else could jump in with suggestions to improve or even alternate solutions, I'd also be interested!

Upvotes: 1

Related Questions