32

我创建了一个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]
4

9 回答 9

77

无论是 bug 还是功能,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;请欺骗。

于 2013-03-06T02:30:11.993 回答
53

如果您在加载视图控制器时尝试滚动,请确保在调用layoutIfNeeded之前UICollectionView调用scrollToItemAtIndexPath. 这比将滚动逻辑放在 viewDidLayoutSubviews 中要好,因为您不会在每次布局父视图的子视图时执行滚动操作。

于 2015-02-26T20:23:40.487 回答
15

有时collectionView(_:didSelectItemAt:)要么不在主线程上调用,要么阻塞它,导致scrollToItem(at:at:animated:)不做任何事情。

通过执行以下操作解决此问题:

DispatchQueue.main.async {
  collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
于 2017-12-18T22:28:05.663 回答
8

从 iOS 9.3、Xcode 8.2.1、Swift 3 开始:

调用scrollToItem(at:)fromviewWillAppear()仍然无法正常工作,尤其是在您使用 Section Headers/Footers、Auto Layout、Section Insets 时。

即使您调用setNeedsLayout()layoutIfNeeded()collectionView行为仍然很无聊。将滚动代码放入动画块并不可靠。

如其他答案所示,解决方案是仅scrollToItem(at:)在确定所有内容都已布置好后才致电。即在viewDidLayoutSubviews().

但是,您需要有选择性;您不想每次viewWillLayoutSubviews()调用时都执行滚动。所以一个解决方案是在 中设置一个标志viewWillAppear(),然后在 中对其进行操作viewDidLayoutSubviews()

IE

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)
        }
    }
}
于 2017-03-22T23:18:18.110 回答
7

你可以在 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,并且在这个属性的设置器上我调用

- (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;
}
于 2014-04-07T11:58:19.107 回答
5

另请记住,您需要使用正确的 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

};

于 2013-08-14T09:01:57.710 回答
3

添加滚动逻辑viewDidAppear为我工作:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    self.collectionView?.scrollToItemAtIndexPath(
        someIndexPath,
        atScrollPosition: UICollectionViewScrollPosition.None,
        animated: animated)
}

添加它viewDidLoad不起作用:它被忽略。

除非您想在任何时间发生任何变化时滚动逻辑调用,否则将其添加到其中viewDidLayoutSubviews不起作用。就我而言,它阻止用户手动滚动项目

于 2016-04-04T20:49:35.033 回答
0

斯威夫特 3

UIView.animate(withDuration: 0.4, animations: {
    myCollectionview.scrollToItem(at: myIndexPath, at: .centeredHorizontally, animated: false)
}, completion: {
    (value: Bool) in
    // put your completion stuff here
})
于 2017-03-10T13:41:35.533 回答
0

这是基于@Womble 的回答,所有功劳归于他们:

该方法viewDidLayoutSubviews()被重复调用。对我(iOS 11.2)来说,它第一次被称为collectionView.contentSizeis {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它工作得很好。

于 2018-01-25T00:28:00.627 回答