I have created a CollectionView
Control and filled it with images. Now I want to scroll to item at a particular index on start. I have tried out scrollToItemA
As of iOS 9.3, Xcode 8.2.1, Swift 3:
Calling scrollToItem(at:)
from viewWillAppear()
is still broken, particularly if you are using Section Headers/Footers, Auto Layout, Section Insets.
Even if you call setNeedsLayout()
and layoutIfNeeded()
on the collectionView
, the behavior is still borked. Putting the scrolling code in to an animation block doesn't work reliably.
As indicated in the other answers, the solution is to only call scrollToItem(at:)
once you are sure everything has been laid out. i.e. in viewDidLayoutSubviews()
.
However, you need to be selective; you don't want to perform scrolling every time viewWillLayoutSubviews()
is called. So a solution is to set a flag in viewWillAppear()
, and act it on it in viewDidLayoutSubviews()
.
i.e.
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)
}
}
}
Adding the scrolling logic to viewDidAppear
worked for me:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.collectionView?.scrollToItemAtIndexPath(
someIndexPath,
atScrollPosition: UICollectionViewScrollPosition.None,
animated: animated)
}
Adding it to viewDidLoad
doesn't work: it gets ignored.
Adding it to viewDidLayoutSubviews
doesn't work unless you want to scroll logic calling any time anything changes. In my case, it prevented the user from manually scrolling the item
Whether it's a bug or a feature, UIKit throws this error whenever scrollToItemAtIndexPath:atScrollPosition:Animated
is called before UICollectionView
has laid out its subviews.
As a workaround, move your scrolling invocation to a place in the view controller lifecycle where you're sure it has already computed its layout, like so:
@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
At the very least, the error message should probably be more helpful. I've opened a rdar://13416281; please dupe.
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
})
U can do this and on viewDidLoad method
just make call 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];
}
}];
In my case my subclass CollectionView has a property selectedPage, and on a setter of this property i call
- (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];
}
}];
}
In view controller i calling this by code
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.segmentedPages.selectedPage = 1;
}
If you are trying to scroll when the view controller is loading, make sure to call layoutIfNeeded
on the UICollectionView
before you call scrollToItemAtIndexPath
. This is better than putting the scroll logic in viewDidLayoutSubviews because you won't perform the scroll operation every time the parent view's subviews are laid out.