我创建了一个CollectionView
控件并用图像填充它。现在我想在开始时滚动到特定索引处的项目。我尝试了scrollToItemAtIndexPath
如下:
[self.myFullScreenCollectionView scrollToItemAtIndexPath:indexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
但是,我得到了以下异常。任何人都可以指导我在哪里出错。
2013-02-20 02:32:45.219 ControlViewCollection1[1727:c07] *** Assertion failure in
-[UICollectionViewData layoutAttributesForItemAtIndexPath:], /SourceCache/UIKit_Sim/UIKit-2380.17
/UICollectionViewData.m:485 2013-02-20 02:32:45.221 ControlViewCollection1[1727:c07] must return a
UICollectionViewLayoutAttributes instance from -layoutAttributesForItemAtIndexPath: for path
<NSIndexPath 0x800abe0> 2 indexes [0, 4]
答案 0 :(得分:71)
无论是错误还是功能,UIKit会在scrollToItemAtIndexPath:atScrollPosition:Animated
布置其子视图之前调用UICollectionView
时抛出此错误。
作为一种解决方法,将滚动调用移动到视图控制器生命周期中您确定已经计算其布局的位置,如下所示:
@implementation CollectionViewControllerSubclass
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// scrolling here doesn't work (results in your assertion failure)
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSIndexPath *indexPath = // compute some index path
// scrolling here does work
[self.collectionView scrollToItemAtIndexPath:indexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:YES];
}
@end
至少,错误消息应该更有帮助。我打开了rdar://13416281;请欺骗。
答案 1 :(得分:49)
如果您在加载视图控制器时尝试滚动,请务必在致电layoutIfNeeded
之前致电UICollectionView
上的scrollToItemAtIndexPath
。这比将视图滚动逻辑放在viewDidLayoutSubviews中要好,因为每次布置父视图的子视图时都不会执行滚动操作。
答案 2 :(得分:12)
有时collectionView(_:didSelectItemAt:)
未在主线程上调用,或阻止它,导致scrollToItem(at:at:animated:)
无法执行任何操作。
通过以下方式解决这个问题:
DispatchQueue.main.async {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
答案 3 :(得分:6)
你可以在viewDidLoad方法
上执行此操作只需调用preformBatchUpdates
[self performBatchUpdates:^{
if ([self.segmentedDelegate respondsToSelector:@selector(segmentedBar:selectedIndex:)]){
[self.segmentedDelegate segmentedBar:self selectedIndex:_selectedPage];
}
} completion:^(BOOL finished) {
if (finished){
[self scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:_selectedPage inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
}];
在我的情况下,我的子类CollectionView有一个属性selectedPage,并且在这个属性的setter上我调用
- (void)setSelectedPage:(NSInteger)selectedPage {
_selectedPage = selectedPage;
[self performBatchUpdates:^{
if ([self.segmentedDelegate respondsToSelector:@selector(segmentedBar:selectedIndex:)]){
[self.segmentedDelegate segmentedBar:self selectedIndex:_selectedPage];
}
} completion:^(BOOL finished) {
if (finished){
[self scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:_selectedPage inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
}];
}
在视图控制器中,我通过代码
调用它- (void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.segmentedPages.selectedPage = 1;
}
答案 4 :(得分:6)
从iOS 9.3开始,Xcode 8.2.1,Swift 3:
从scrollToItem(at:)
调用viewWillAppear()
仍然会被破坏,特别是如果您使用剖面页眉/页脚,自动布局,剖面插图。
即使您在setNeedsLayout()
上致电layoutIfNeeded()
和collectionView
,行为仍然存在。将滚动代码放入动画块中无法可靠地工作。
如其他答案中所示,解决方案是只在确定所有内容都已布置后才调用scrollToItem(at:)
。即在viewDidLayoutSubviews()
。
但是,你需要有选择性;您不希望每次调用viewWillLayoutSubviews()
时都执行滚动操作。因此,解决方案是在viewWillAppear()
中设置一个标记,并在viewDidLayoutSubviews()
中对其执行操作。
即
fileprivate var needsDelayedScrolling = false
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.needsDelayedScrolling = true
// ...
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
if self.needsDelayedScrolling {
self.needsDelayedScrolling = false
self.collectionView!.scrollToItem(at: someIndexPath,
at: .centeredVertically,
animated: false)
}
}
}
答案 5 :(得分:4)
另请记住,您需要使用正确的UICollectionviewScrollPosition值。请参阅以下代码以获得澄清:
typedef NS_OPTIONS(NSUInteger, UICollectionViewScrollPosition) {
UICollectionViewScrollPositionNone = 0,
/* For a view with vertical scrolling */
// The vertical positions are mutually exclusive to each other, but are bitwise or-able with the horizontal scroll positions.
// Combining positions from the same grouping (horizontal or vertical) will result in an NSInvalidArgumentException.
UICollectionViewScrollPositionTop = 1 << 0,
UICollectionViewScrollPositionCenteredVertically = 1 << 1,
UICollectionViewScrollPositionBottom = 1 << 2,
/* For a view with horizontal scrolling */
// Likewise, the horizontal positions are mutually exclusive to each other.
UICollectionViewScrollPositionLeft = 1 << 3,
UICollectionViewScrollPositionCenteredHorizontally = 1 << 4,
UICollectionViewScrollPositionRight = 1 << 5
};
答案 6 :(得分:3)
将滚动逻辑添加到viewDidAppear
对我有用:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.collectionView?.scrollToItemAtIndexPath(
someIndexPath,
atScrollPosition: UICollectionViewScrollPosition.None,
animated: animated)
}
将其添加到viewDidLoad
不起作用:它会被忽略。
将其添加到viewDidLayoutSubviews
不起作用,除非你想在任何时候发生任何变化时滚动逻辑调用。就我而言,它阻止用户手动滚动项目
答案 7 :(得分:0)
Swift 3
UIView.animate(withDuration: 0.4, animations: {
myCollectionview.scrollToItem(at: myIndexPath, at: .centeredHorizontally, animated: false)
}, completion: {
(value: Bool) in
// put your completion stuff here
})
答案 8 :(得分:0)
这是基于@Womble的回答,所有的归功于他们:
重复调用方法viewDidLayoutSubviews()
。对我来说(iOS 11.2),第一次调用时collectionView.contentSize
是{0,0}
。第二次,contentSize
是正确的。因此,我必须为此添加一个检查:
var needsDelayedScrolling = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.needsDelayedScrolling = true
// ...
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if self.needsDelayedScrolling && collectionView.contentSize.width > 0 {
self.needsDelayedScrolling = false
self.collectionView!.scrollToItem(at: someIndexPath,
at: .centeredVertically,
animated: false)
}
}
}
添加额外的&& collectionView.contentSize.width > 0
后,它的效果非常好。