Reputation: 656
I have tried many solutions but none of them work. The question is simple — how to scroll down during viewDidLoad
, viewWillAppear
or viewDidAppear
without visual(jump) effect?
What I have now:
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if (!self.layoutFlag) {
if let lastCell: IndexPath = self.lastCellIndexPath {
self.collectionView?.scrollToItem(at: lastCell, at: UICollectionViewScrollPosition.bottom, animated: true)
self.layoutFlag = true
}
}
}
Upvotes: 7
Views: 17903
Reputation: 11
Swift 5
I found this solution for myself
func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
let maxYPosition = collectionView.collectionViewLayout.collectionViewContentSize.height
let collectionViewHeight = collectionView.bounds.height
return CGPoint(x: 0, y: maxYPosition - collectionViewHeight)
}
Upvotes: 1
Reputation: 223
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
}
}
Just wrap scrollToItem
within an async block. It works perfectly, no visual transition and easy to apply.
How it worked I can only guess. Synchronously scroll the collection view in viewDidLoad will not do because it hasn't been laid out yet. Commit scroll in an async block adds this action to the next runloop task, when next runloop executes, which is exactly when ui will be drawn, the scroll executes right after, and displayed in the same frame.
All a above is just a guess, anyone has any idea I'd be glad to learn.
Upvotes: 8
Reputation: 1141
I found only one way to set initial scroll position:
call my scroll function in viewDidLayoutSubviews
var isScrolled = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !isScrolled && levelCollectionView.visibleCells.count > 0 {
isScrolled = true
// scroll
}
}
it doesn't look as correct answer but it works
Upvotes: 9
Reputation: 1865
Add the scroll to index path call to the viewDidAppear(_:)
method and have the animated
parameter set to false
:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !self.layoutFlag {
if let lastCell: IndexPath = self.lastCellIndexPath {
self.collectionView?.scrollToItem(at: lastCell, at: UICollectionViewScrollPosition.bottom, animated: false)
self.layoutFlag = true
}
}
}
The collection view should be scrolled to the bottom before the view is shown to the user, thus no "jump".
Upvotes: -1
Reputation: 2443
Checked in a test project, works in viewWillAppear
. I've added 3 screens, 1-st is clean, second contains collectionView and the last one is empty.
Implementation of the second screen is below (viewWillAppear triggers UIScrollView extension method and scrolls content):
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.scrollToBottom(animated: false)
}
}
extension ViewController: UICollectionViewDelegate,
UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 200
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
}
Here's an extension
for scrolling content:
extension UIScrollView {
func scrollToTop(animated: Bool) {
setContentOffset(CGPoint(x: 0, y: -contentInset.top),
animated: animated)
}
func scrollToBottom(animated: Bool) {
setContentOffset(CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude),
animated: animated)
}
}
Try to make a new clean
project and play with it.
Upvotes: 3