I use a standard UICollectionView
with sections. My cells are laid out like a grid. The next cell in a chosen direction is correctly focused if the user moves the focus around with the Apple TV remote. But if there is a "gap" in the grid, the default focus engine jumps over sections. It does this to focus a cell which could be several sections away but is in the same column.
Simple Example: There are 3 sections. The first section has 3 cells. The second has 2 cells and the last one has 3 cells again. See the following image:
If the green cell is focused and the user touches the down direction, the yellow cell gets focused and section two is skipped by the focus engine.
I would like to force it that no sections can get jumped over. So instead of focusing the yellow cell I would like to focus the blue cell.
I learned that the Apple TV Focus engine internally works like a grid system and that the described behaviour is the default one. To allow other movements (e.g. diagonal) we need to help the focus engine by placing invisible UIFocusGuide
s which can redirect the focus engine to a preferredFocusedView
.
So in the following image there is one invisible red focus guide placed into the empty space of a UICollectionView
section which would redirect the down focus to the desired blue cell. I think that would be the perfect solution, in theorie.
But how would I add UIFocusGuide
s to all empty spaces of UICollectionView
sections? I have tried several things but nothing worked. Maybe add it as a Decorator View but that seems wrong. Or as additional cells, but that breaks the data layer and the constraints anchors do not work.
Has anyone an idea on how to add UIFocusGuide
s to a UICollectionView
?
One solution is to subclass UICollectionViewFlowLayout
with a layout that adds Supplementary Views with UIFocusGuide
s on top for all empty areas.
Basically the custom flow layout calculates the needed layout attributes in the prepareLayout
like this:
self.supplementaryViewAttributeList = [NSMutableArray array];
if(self.collectionView != nil) {
// calculate layout values
CGFloat contentWidth = self.collectionViewContentSize.width - self.sectionInset.left - self.sectionInset.right;
CGFloat cellSizeWithSpacing = self.itemSize.width + self.minimumInteritemSpacing;
NSInteger numberOfItemsPerLine = floor(contentWidth / cellSizeWithSpacing);
CGFloat realInterItemSpacing = (contentWidth - (numberOfItemsPerLine * self.itemSize.width)) / (numberOfItemsPerLine - 1);
// add supplementary attributes
for (NSInteger numberOfSection = 0; numberOfSection < self.collectionView.numberOfSections; numberOfSection++) {
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:numberOfSection];
NSInteger numberOfSupplementaryViews = numberOfItemsPerLine - (numberOfItems % numberOfItemsPerLine);
if (numberOfSupplementaryViews > 0 && numberOfSupplementaryViews < 6) {
NSIndexPath *indexPathOfLastCellOfSection = [NSIndexPath indexPathForItem:(numberOfItems - 1) inSection:numberOfSection];
UICollectionViewLayoutAttributes *layoutAttributesOfLastCellOfSection = [self layoutAttributesForItemAtIndexPath:indexPathOfLastCellOfSection];
for (NSInteger numberOfSupplementor = 0; numberOfSupplementor < numberOfSupplementaryViews; numberOfSupplementor++) {
NSIndexPath *indexPathOfSupplementor = [NSIndexPath indexPathForItem:(numberOfItems + numberOfSupplementor) inSection:numberOfSection];
UICollectionViewLayoutAttributes *supplementaryLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:ARNCollectionElementKindFocusGuide withIndexPath:indexPathOfSupplementor];
supplementaryLayoutAttributes.frame = CGRectMake(layoutAttributesOfLastCellOfSection.frame.origin.x + ((numberOfSupplementor + 1) * (self.itemSize.width + realInterItemSpacing)), layoutAttributesOfLastCellOfSection.frame.origin.y, self.itemSize.width, self.itemSize.height);
supplementaryLayoutAttributes.zIndex = -1;
[self.supplementaryViewAttributeList addObject:supplementaryLayoutAttributes];
}
}
}
}
and then returns the needed layouts in the layoutAttributesForSupplementaryViewOfKind:
method like this:
if ([elementKind isEqualToString:ARNCollectionElementKindFocusGuide]) {
for (UICollectionViewLayoutAttributes *supplementaryLayoutAttributes in self.supplementaryViewAttributeList) {
if ([indexPath isEqual:supplementaryLayoutAttributes.indexPath]) {
layoutAttributes = supplementaryLayoutAttributes;
}
}
}
Now your Supplementary Views just need a UIFocusGuide
the same size as the supplementary view itself. That's it.
A full implementation of the method described can be found here on gitHub