UICollectionView 的自定义焦点引擎行为
Custom focus engine behaviour for UICollectionView
我使用带部分的标准 UICollectionView
。我的细胞像网格一样布置。如果用户使用 Apple TV 遥控器四处移动焦点,则所选方向的下一个单元格会正确聚焦。但如果网格中有 "gap",则默认焦点引擎会跳过部分。它这样做是为了聚焦一个单元格,该单元格可能远离几个部分但在同一列中。
简单示例:
有3个部分。第一部分有 3 个单元格。第二个有 2 个单元格,最后一个有 3 个单元格。见下图:
如果绿色单元格获得焦点并且用户触摸向下方向,黄色单元格获得焦点并且焦点引擎跳过第二部分。
我想强制它没有任何部分可以跳过。因此,我不想聚焦黄色单元格,而是聚焦蓝色单元格。
我了解到 Apple TV Focus 引擎在内部像网格系统一样工作,并且所描述的行为是默认行为。为了允许其他移动(例如对角线),我们需要通过放置不可见的 UIFocusGuide
s 来帮助焦点引擎,它可以将焦点引擎重定向到 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
我使用带部分的标准 UICollectionView
。我的细胞像网格一样布置。如果用户使用 Apple TV 遥控器四处移动焦点,则所选方向的下一个单元格会正确聚焦。但如果网格中有 "gap",则默认焦点引擎会跳过部分。它这样做是为了聚焦一个单元格,该单元格可能远离几个部分但在同一列中。
简单示例: 有3个部分。第一部分有 3 个单元格。第二个有 2 个单元格,最后一个有 3 个单元格。见下图:
如果绿色单元格获得焦点并且用户触摸向下方向,黄色单元格获得焦点并且焦点引擎跳过第二部分。
我想强制它没有任何部分可以跳过。因此,我不想聚焦黄色单元格,而是聚焦蓝色单元格。
我了解到 Apple TV Focus 引擎在内部像网格系统一样工作,并且所描述的行为是默认行为。为了允许其他移动(例如对角线),我们需要通过放置不可见的 UIFocusGuide
s 来帮助焦点引擎,它可以将焦点引擎重定向到 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