Reputation: 2246
I have a UICollectionView
within a UIViewController
with paging enabled. For some strange reason collectionView.scrollToItem
works when the direction of the collectionview
is vertical
but doesn't when direction is horizontal
. Is this there something I'm doing wrong or is this supposed to happen?
//Test scrollToItem
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let i = IndexPath(item: 3, section: 0)
collectionView.reloadData()
collectionView.scrollToItem(at: i, at: .top, animated: true)
print("Selected")
}
Upvotes: 55
Views: 61299
Reputation: 1
CollectionView.isPagingEnabled = false
CollectionView.scrollToItem(at:IndexPath(item: YourcellIndex(4), section: at which section you want cell(0)), at: .centeredHorizontally, animated: false)
CollectionView.layoutSubviews()
CollectionView.isPagingEnabled = true
Upvotes: 0
Reputation: 9351
As @Dheeraj Agarwal points out, none of the 'ScrollTo' functionality of UICollectionView
OR UIScrollView
will seem to work properly if it is called before the view has been laid out. To be accurate, I think they WORK, but the effects are immediately nullified because laying out a UICollectionView
causes it to reset to its minimum bounds, probably because of all the cell layout functions that will trigger, and the fact its content size may change.
The solution is to make sure this function is called after layout occurs, but it's not that simple. It's entirely likely that a collection view may be told to layout its content again and again in response to various changes - setting delegates, the contents updating, the view controller being added to a parent and therefore changing size. Each time this happens it'll reset to 0:0 offset.
You'll therefore have to keep a reference to the desired offset / cell index / frame until such a time as you are CERTAIN there will be no more unexpected layout updates. You can't just nil it out immediately as your collection view's layout might change multiple times before the view appears. I'm currently storing a frame in an attribute and calling the function in layoutFrames
every time (my collection view's parent is a custom view, not a view controller). Although this has the slightly annoying feature of scrolling back again if the user rotates their phone, I consider it acceptable since this is a custom keyboard and most users will work with it in one orientation or the other, they won't keep flipping their phone around just to select a single value.
Solutions like calling DispatchQueue.main.asyncAfter
are fragile. They work because the function call gets delayed until after the first layout occurs, but this may not always-and-forever solve the problem.
I guess the 'Scroll To' functions were only ever intended to be used in response to direct user input after the collection view is already populated.
Upvotes: 0
Reputation: 51
This will not work if you call scrollToItem
before viewDidLayoutSubviews
. Using scrollToItem
in viewDidLoad
will not work. So call this function after viewDidLayoutSubviews()
is completed.
Upvotes: 5
Reputation: 1736
For iOS 14+
It's so stupid but it works.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.collectionView.scrollToItem(at: i, at: .top, animated: true)
}
Upvotes: 1
Reputation: 319
I have this issue: when button tapped (for horizontal collection scroll to next item) it always returns to first item with UI bug.
The reason was in this parameter: myCollection.isPagingEnabled = true
Solution: just disable paging before scroll to next item, and enable it after scroll.
Upvotes: 10
Reputation: 282
I try a lot of things and fail. This saves my day.
func scrollToIndex(index:Int) {
let rect = self.collectionView.layoutAttributesForItem(at: IndexPath(row: index, section: 0))?.frame
self.collectionView.scrollRectToVisible(rect!, animated: true)
}
Reference: https://stackoverflow.com/a/41413575/9047324
Upvotes: 9
Reputation: 9
sometimes you can set
collectionView.collectionlayout.invalidate()
collectionView.scrollToItem(row:2,section:0)
If you have networking which can make disturb main thread, you have to avoid or after finish that, have to apply this code. for example
self.group.enter()
some thread
self.group.leave()
Upvotes: -2
Reputation: 135
After adding items to collectionView and reloadData()
, scrollToItem
was not working because reloading data has not finished yet.
Because of this I added performBatchUpdates like this :
self.dataSource.append("Test")
self.collectionView.performBatchUpdates ({
self.collectionView.reloadData()
}, completion: { _ in
self.collectionView.scrollToItem(at: IndexPath(item: 3, section: 0),
at: .centeredHorizontally,
animated: false)
})
I know it's not about this question but it will be helpful for this title.
Upvotes: 8
Reputation: 5640
Apparently there is a new bug in UICollectionView
that is causing scrollToItem
to not work when paging is enabled. The work around is to disable paging before calling scrollToItem
, then re-enabling it afterwards:
collectionView.isPagingEnabled = false
collectionView.scrollToItem(
at: IndexPath(item: value, section: 0),
at: .centeredHorizontally,
animated: true
)
collectionView.isPagingEnabled = true
Source: https://developer.apple.com/forums/thread/663156
Upvotes: 100
Reputation: 1034
For me, I had to scroll collection view on main thread like:
DispatchQueue.main.async {
self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
Upvotes: 11
Reputation: 145
Swift 5 if with animation horizontally
func scrollToIndex(index:Int) {
self.collectionView?.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
}
Your example:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
print("Selected: \(indexPath.row)")
collectionView.reloadData()
}
Upvotes: -1
Reputation: 1565
Swift 5.1, Xcode 11.4
collectionView.scrollToItem(at: IndexPath(item: pageNumber , section: 0), at: .centeredHorizontally, animated: true)
self.collectionView.setNeedsLayout() // **Without this effect wont be visible**
Upvotes: 11
Reputation: 14294
Swift 3:
This worked for me on horizontal collection view.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//Show 14th row as first row
self.activityCollectionView?.scrollToItem(at: IndexPath(row: 14, section: 0), at: UICollectionViewScrollPosition.right, animated: true)
}
You can choose whether the desired cell at index to be on right or left on scroll position.
UICollectionViewScrollPosition.right or UICollectionViewScrollPosition.left.
Upvotes: 1
Reputation: 1473
I had trouble implementing this in a flow layout with entered paging per item. The .centeredHorizontally just wouldn't work for me so i use scroll to rect and Check there is data before scrolling:
if self.collectionView?.dataSource?.collectionView(self.collectionView!, cellForItemAt: IndexPath(row: 0, section: 0)) != nil {
let rect = self.collectionView.layoutAttributesForItem(at: IndexPath(item: data[index], section: 0))?.frame
self.collectionView.scrollRectToVisible(rect!, animated: false)
}
Upvotes: 19
Reputation: 2055
For this part:
collectionView.scrollToItem(at: i, at: .top, animated: true)
When the scroll direction is horizontal
you need to use at: left
, at: right
or at: centeredHorizontally
. at: top
is for vertical
direction.
Upvotes: 48