以编程方式滚动到 UICollectionView 中的补充视图
Posted
技术标签:
【中文标题】以编程方式滚动到 UICollectionView 中的补充视图【英文标题】:Programmatically scroll to a supplementary view within UICollectionView 【发布时间】:2013-04-26 22:39:51 【问题描述】:我正在使用UICollectionView
分段显示照片。每个部分都有一个补充视图作为标题,并通过以下方法提供:viewForSupplementaryElementOfKind
。
我在侧面有一个洗涤器,允许用户从一个部分跳到另一个部分。现在我正在使用scrollToItemAtIndexPath:atScrollPosition:animated:
滚动到该部分中的第一项,但我真正想要的是滚动collectionView 以便该部分的标题位于屏幕顶部,而不是第一个单元格。我没有看到一个明显的方法来做到这一点。你们有人有解决办法吗?
我想我可以滚动到该部分的第一项,然后通过补充高度加上项目和标题之间的偏移量来抵消它(有一种滚动到点坐标的方法内容视图)。但是,如果有更简单的方法,我想知道。
谢谢。
【问题讨论】:
它有什么问题?它应该很好用,因为每个补充视图都有它的索引路径 是的,他们确实有 indexPaths。我为准备单元格和补充单元格的两种委托方法打印了输出。 indexPaths 不是互斥的,而是它们看起来像两个具有不同 indexPaths 的 distict 集。这可能不会很好地粘贴在这里,但是:!!!单元格索引路径 0 0 !!!支持索引路径 0 0 !!!单元格索引路径 0 1 !!!单元格索引路径 0 2 !!!单元格索引路径 0 3 !!!单元格索引路径 0 4 !!!单元格索引路径 0 5 !!!单元格索引路径 0 6 !!!单元格索引路径 0 7 !!!单元格索引路径 0 8 !!!单元格索引路径 0 9 !!!单元格索引路径 0 10 !!!单元格索引路径 0 11 我应该声明我正在打印 indexPath.section indexPath.item。是否有另一个我忽略的属性可以将它们区分开来?你可以看到 [0 0] 在我打印的单元格视图和补充视图中。 【参考方案1】:您不能为此使用scrollToItemAtIndexPath:atScrollPosition:animated
。
希望他们将来会添加一个像scrollToSupplementaryElementOfKind:atIndexPath:
这样的新方法,但目前唯一的方法是直接操作 contentOffset。
下面的代码显示了如何使用 FlowLayout 将标题滚动到垂直顶部。您可以对水平滚动执行相同的操作,或将此想法用于其他布局类型。
NSIndexPath *indexPath = ... // indexPath of your header, item must be 0
CGFloat offsetY = [collectionView layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath].frame.origin.y;
CGFloat contentInsetY = self.contentInset.top;
CGFloat sectionInsetY = ((UICollectionViewFlowLayout *)collectionView.collectionViewLayout).sectionInset.top;
[collectionView setContentOffset:CGPointMake(collectionView.contentOffset.x, offsetY - contentInsetY - sectionInsetY) animated:YES];
请注意,如果您有非零 contentInset
(如在 ios 7 中,当滚动视图在栏下方展开时),您需要从 offsetY
中减去它,如图所示。 sectionInset
也一样。
更新:
代码假定布局处于准备好的“有效”状态,因为它使用它来计算偏移量。当集合视图呈现其内容时,布局就准备好了。
当您需要滚动尚未呈现的集合视图时(来自viewDidLoad
说),在上面的代码之前调用[_collectionView.collectionViewLayout prepareLayout]
可能会有所帮助。对layoutIfNeeded
的调用(正如@Vrasidas 在 cmets 中建议的那样)也应该可以工作,因为它还准备了布局。
【讨论】:
这对我不起作用。它在 layoutAttributes 崩溃...Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'layout attributes for supplementary item at index path ([0, 0]) changed from <UICollectionViewLayoutAttributes: 0x12c8d180> index path: ([0, 0]); element kind: (UICollectionElementKindSectionHeader); frame = (0 0; 0 0); to <UICollectionViewLayoutAttributes: 0x12cc99e0> index path: ([0, 0]); element kind: (UICollectionElementKindSectionHeader); frame = (0 0; 492 60); without invalidating the layout'
有什么想法吗?
我发现了丢失的东西...调用 [collectionView layoutIfNeeded];在那之前解决了这个问题,现在它可以工作了!
一张便条。在构建 NSIndexPath 时,请务必将“项目”设置为 0。我刚刚修复了使用非零“项目”值导致 UICollectionView 在显示时请求两个补充单元格的错误。当您将该视图滚动到屏幕外时,集合视图只会删除一个。
我提交了 rdar://16572180 询问是否有人想欺骗 BTW
如果您的部分比屏幕的高度短,这可能会滚动到 collectionView.contentSize 边界之外。我将创建名为 contentOffsetY 和 maxContentOffsetY 的变量:CGFloat contentOffsetY = offsetY - contentInsetY - sectionInsetY; CGFloat maxContentOffsetY = self.collectionView.contentSize.height - self.collectionView.frame.size.height;
然后您可以更改您的 contentOffsetY:if (maxContentOffsetY < 0) maxContentOffsetY = 0; if (contentOffsetY > maxContentOffsetY) contentOffsetY = maxContentOffsetY;
【参考方案2】:
Swift 中的解决方案,
let section: Int = 0 // Top
if let cv = self.collectionView
cv.layoutIfNeeded()
let indexPath = IndexPath(row: 1, section: section)
if let attributes = cv.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath)
let topOfHeader = CGPoint(x: 0, y: attributes.frame.origin.y - cv.contentInset.top)
cv.setContentOffset(topOfHeader, animated: true)
致 Gene De Lisa 的道具:http://www.rockhoppertech.com/blog/scroll-to-uicollectionview-header/
【讨论】:
此代码有效,但正如此页面上的其他解决方案所建议的那样,您需要在调用cv.layoutAttributesForSupplementaryElementOfKind
之前放置一个 cv.layoutIfNeeded()
函数,否则如果 cv 未准备好,此代码将崩溃。
【参考方案3】:
// scroll to selected index
NSIndexPath* cellIndexPath = [NSIndexPath indexPathForItem:0 inSection:sectionIndex];
UICollectionViewLayoutAttributes* attr = [self.collectionView.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:cellIndexPath];
UIEdgeInsets insets = self.collectionView.scrollIndicatorInsets;
CGRect rect = attr.frame;
rect.size = self.collectionView.frame.size;
rect.size.height -= insets.top + insets.bottom;
CGFloat offset = (rect.origin.y + rect.size.height) - self.collectionView.contentSize.height;
if ( offset > 0.0 ) rect = CGRectOffset(rect, 0, -offset);
[self.collectionView scrollRectToVisible:rect animated:YES];
【讨论】:
【参考方案4】:解决方案无法解决的一件事是,如果您要固定部分标题。这些方法适用于未固定的标题,但如果您的标题已固定,则在滚动到当前部分上方的部分时,一旦出现部分标题(这将是您部分的底行),它将停止。在某些情况下这可能是可取的,但我认为目标是将部分的顶部放在屏幕顶部。
在这种情况下,您需要采用上述方法并稍作调整。例如:
UICollectionView *cv = self.collectionView;
CGFloat contentInsetY = cv.contentInset.top;
CGFloat offsetY = [cv layoutAttributesForItemAtIndexPath:ip].frame.origin.y;
CGFloat sectionHeight =
[cv layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:ip].frame.size.height;
[cv setContentOffset:CGPointMake(cv.contentOffset.x, offsetY - contentInsetY - sectionHeight) animated:YES];
现在,您基本上是将部分的第一行滚动到可见,减去部分标题的高度。这会将部分标题放在您想要的顶部,并带有固定标题,这样滚动的方向就不再重要了。我没有使用部分插图进行测试。
【讨论】:
以上是关于以编程方式滚动到 UICollectionView 中的补充视图的主要内容,如果未能解决你的问题,请参考以下文章
你能以编程方式修改 UICollectionView 滚动方向吗?
以编程方式创建的 UICollectionView:滚动后单元格重叠
以编程方式选择尚未加载的 UICollectionView 单元格
如何根据 UICollectionView 的部分以编程方式更新标签