UICollectionView 的自定义焦点引擎行为

Custom focus engine behaviour for UICollectionView

我使用带部分的标准 UICollectionView。我的细胞像网格一样布置。如果用户使用 Apple TV 遥控器四处移动焦点,则所选方向的下一个单元格会正确聚焦。但如果网格中有 "gap",则默认焦点引擎会跳过部分。它这样做是为了聚焦一个单元格,该单元格可能远离几个部分但在同一列中。

简单示例: 有3个部分。第一部分有 3 个单元格。第二个有 2 个单元格,最后一个有 3 个单元格。见下图:

如果绿色单元格获得焦点并且用户触摸向下方向,黄色单元格获得焦点并且焦点引擎跳过第二部分。

我想强制它没有任何部分可以跳过。因此,我不想聚焦黄色单元格,而是聚焦蓝色单元格。

我了解到 Apple TV Focus 引擎在内部像网格系统一样工作,并且所描述的行为是默认行为。为了允许其他移动(例如对角线),我们需要通过放置不可见的 UIFocusGuides 来帮助焦点引擎,它可以将焦点引擎重定向到 preferredFocusedView

所以在下图中,有一个不可见的红色焦点引导放置在 UICollectionView 部分的空 space 中,它将向下焦点重定向到所需的蓝色单元格。我认为这在理论上是完美的解决方案。

但是我如何将 UIFocusGuide 添加到 UICollectionView 部分的所有空 space?我尝试了几件事,但没有任何效果。也许将它添加为装饰器视图,但这似乎是错误的。或者作为额外的单元格,但这会破坏数据层并且约束锚点不起作用。

有没有人知道如何将 UIFocusGuide 添加到 UICollectionView

实现目标的两种方法。至少我尝试过并使它起作用。 =).可能还有其他方法。

1:

实现目标的最简单方法是添加垂直 collection 视图,其中的单元格包含水平 collection 视图。每个水平 collection 是您的部分。

确保将以下代码添加到垂直 collection 视图:

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

2:

如果你想使用 UIFocusGuide,我认为添加焦点指南的好地方是 header 部分。确保您的焦点指南位于 header 部分,并将 preferredFocusedView 更新为切换到每个部分。在我的例子中,当我离开该部分时,我分配了每个部分的第一个单元格。

一个解决方案是使用布局为 UICollectionViewFlowLayout 子类化,为所有空白区域添加带有 UIFocusGuide 的补充视图。

基本上,自定义流布局会像这样计算 prepareLayout 中所需的布局属性:

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];
            }
        }
    }
}

然后 returns 在 layoutAttributesForSupplementaryViewOfKind: 方法中需要的布局如下:

if ([elementKind isEqualToString:ARNCollectionElementKindFocusGuide]) {
    for (UICollectionViewLayoutAttributes *supplementaryLayoutAttributes in self.supplementaryViewAttributeList) {
        if ([indexPath isEqual:supplementaryLayoutAttributes.indexPath]) {
            layoutAttributes = supplementaryLayoutAttributes;
        }
    }
}

现在您的补充视图只需要 UIFocusGuide 与补充视图本身大小相同。而已。

可以找到所描述方法的完整实现 here on gitHub

也许您可以尝试实现 optional func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? 方法,它是 UICollectionViewDelegate 的一部分?

为您的第 1、2、3 节实施此方法...UICollectionView 和 returns,如果您希望焦点引擎自动聚焦第一个项目,假设 IndexPath(item: 0, section: 0) .

请注意,如果您还想设置 sectionCollectionView.remembersLastFocusedIndexPath= true