How to preserve UICollectionView scroll position when rotating

I am using UICollectionView to populate almost 30 cells.


In portrait mode on iPhone there is only one column i.e. one cell lauout & in landscape mode there is two columns i.e. two cells horizontally layout at given point of time.


Scrolling is vertical.


But when I am middle of the scroll & then I rotate to landscape mode, Scroll position get sets to end of the screen.


How to fix that?


I searched the Google but did not got correct answer.

Replies

Probably because in landscape, this scroll position corresponds to the last, hence the new scroll value.


I would follow this logic:


- keep track of what is the top leftmost cell

- when rotating, calculate what should be scroll value to keep this cell at topleft

- adjust the scroll position accordingly

Can you give referance code... or link so that I can know it better...

I have no reference code. Just told you the logic I would use to write this code.


Did you try to write it ?

I really don't have idea where to wite this code in what function!


But I got to know reason that may be causing this issue...


In portrait mode there are verticaly 1 & half cells vertically & in landscape mode there is two full cells horizontally. So having wrong & missmatched cells at both orientation that may be causing the problem.


What you think?

I would implement it in


override func viewWillLayoutSubviews() { }

Can any body able to guide me to solve this problem?


Any help or any helpful code example would be appreciated...

Did you try to implement in viewWillLayoutSubviews ?


I don't think this is complicated, just need to write some code

- keep track of what is the top leftmost cell:

Use visibleCells

If you have defined a tag in cells, then you can find which is the topmost

keep the IndexPath of this cell

- when rotating, calculate what should be scroll value to keep this cell at topleft

This depends on how many cells you can display horizontally

- adjust the scroll position accordingly

use scrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool)

to move correctly the collectionView

Did you make it work ?

Sorry but I really don't have a clue to how to write a code for this issue...

If one day I have to write such code, I will post it here…


Don't think it is very complicated, but sure requires some coding.


Update: here some code. I assembled a very simple test. The IB set up is very simple: a collectionView in the window ; set delegate and dataSource to the viewController. Cells identifier is yellowCollectionCell


import UIKit

class WithCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    @IBOutlet weak var yellowCollection: UICollectionView!
   
    private var items: [Int] = Array(repeating: 0, count: 24)
   
    var topLeftCollectionItem: Int?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
   
    let cellWidth = CGFloat(250) // To have 1 cell if portrait and 2 cells if landscape
   
    let cellMinimalSpace = CGFloat(10)
   
   
    var numberOfCellsInRow : Int {
        let collectionWidth = yellowCollection.frame.width
        let cellWithWithMinMargin = cellWidth + cellMinimalSpace
        let maxNumber = Int(collectionWidth / cellWithWithMinMargin)
        return maxNumber
    }
   
// At willLayout, we have not yet rotated ; let's keep note of which is the first visible cell
    override func viewWillLayoutSubviews() {
       
        let visibleCells = yellowCollection.visibleCells.sorted { $0.tag < $1.tag} // Need to reorder, as visibleCells array order is not guaranteed

        topLeftCollectionItem = visibleCells.first?.tag ?? 0
       
  
// At didLayout, we have roitated ; let us reposition the scroll at the right cell and make this cell the first displayed }
   
    override func viewDidLayoutSubviews() {
       
        let visibleCells = yellowCollection.visibleCells.sorted { $0.tag < $1.tag}
        let firstIndexPath = IndexPath(item: topLeftCollectionItem ?? 0, section: 0)
       
        yellowCollection.scrollToItem(at: firstIndexPath,
                                      at : .top,
                                      animated: true)
    }
   
    // MARK: - CollectionView delegate
   
    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }
   
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count // howManyItems
    }
   
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
       
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "yellowCollectionCell", for: indexPath)
        switch indexPath.row {
        case 0...4: cell.backgroundColor = UIColor.blue. // Just to notice which cell it is
        case 5...9: cell.backgroundColor = UIColor.red
        case 10...14: cell.backgroundColor = UIColor.yellow
        case 15...19: cell.backgroundColor = UIColor.green
        default : cell.backgroundColor = UIColor.gray
        }
       
        cell.tag = indexPath.row
       
        return cell
    }
   
}