danilabagroff
danilabagroff

Reputation: 656

Set initial scroll position of the collection view

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

Answers (5)

BrinyPiny
BrinyPiny

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

LiLi Kazine
LiLi Kazine

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

Alexandr Vysotsky
Alexandr Vysotsky

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

Taylor M
Taylor M

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

Oleh Zayats
Oleh Zayats

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

Related Questions