Full width UICollectionViewCells overlap during interface orientation rotations causing ugly animation

Preface

Upon rotating the interface, the UICollectionViewCells overlap, generating an unpleasant animation that for sure can't be used in production.

The code

The code was executed on iPhone 6S (NN0W2TU/A A1688) with iOS 15.8.2. I could reproduce the issue on iPhone 15 Pro with iOS 17 on simulator as well.

SelfConfiguringCell.swift:

import UIKit

protocol SelfConfiguringCell: UICollectionViewCell {
    static var reuseIdentifier: String { get }
    
    func configure(with image: String)
}

ISVImageScrollView.swift: Code here

CarouselCell.swift:

import UIKit
import SnapKit

class CarouselCell: UICollectionViewCell, SelfConfiguringCell, UIScrollViewDelegate {
    static var reuseIdentifier: String = "carousel.cell"
    
    internal var image: String = "placeholder" {
        didSet {
            self.imageView = UIImageView(image: UIImage(named: image))
            self.scrollView.imageView = self.imageView
        }
    }
    
    let scrollView: ISVImageScrollView = {
        let scrollView = ISVImageScrollView()
        scrollView.minimumZoomScale = 1.0
        scrollView.maximumZoomScale = 30.0
        scrollView.zoomScale = 1.0
        scrollView.contentOffset = .zero
        scrollView.bouncesZoom = true
        
        return scrollView
    }()
    
     var imageView: UIImageView = {
        let image = UIImage(named: "placeholder")!
        let imageView = UIImageView(image: image)

        return imageView
    }()
    
    func setImage(_ image: String) {
        self.image = image
    }
    
    func configure(with image: String) {
        self.setImage(image)
        
        self.scrollView.snp.makeConstraints { make in
            make.left.top.right.bottom.equalTo(contentView)
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.backgroundColor = UIColor.black
        scrollView.delegate = self

        scrollView.imageView = self.imageView
        contentView.addSubview(scrollView)
    }
    
    required init?(coder: NSCoder) {
        fatalError("Cannot init from storyboard")
    }
    
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return self.imageView
    }
}

ViewController:

import UIKit

class ViewController: UICollectionViewController {
    var currentPage: IndexPath? = nil
    
    let images = ["police", "shutters", "depot", "cakes", "sign"]
    
    init() {        
        let compositionalLayout = UICollectionViewCompositionalLayout { sectionIndex, environment in
            
            let absoluteW = environment.container.effectiveContentSize.width
            let absoluteH = environment.container.effectiveContentSize.height
            
            // Handle landscape
            if absoluteW > absoluteH {
                print("landscape")
                let itemSize = NSCollectionLayoutSize(
                    widthDimension: .fractionalWidth(1),
                    heightDimension: .fractionalHeight(1)
                )
                
                let item = NSCollectionLayoutItem(layoutSize: itemSize)
                
                let groupSize = NSCollectionLayoutSize(
                    widthDimension: .fractionalWidth(1),
                    heightDimension: .fractionalHeight(1)
                )
                
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
                
                let section = NSCollectionLayoutSection(group: group)
                return section
            } else {
                // Handle portrait
                
                print("portrait")
                let itemSize = NSCollectionLayoutSize(
                    widthDimension: .fractionalWidth(1.0),
                    heightDimension: .absolute(absoluteW * 9.0/16.0)
                )
                
                let item = NSCollectionLayoutItem(layoutSize: itemSize)
                
                let groupSize = NSCollectionLayoutSize(
                    widthDimension: .fractionalWidth(1.0),
                    heightDimension: .absolute(absoluteW * 9.0/16.0)
                )
                
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
                
                let section = NSCollectionLayoutSection(group: group)
                return section
            }
        }

        let config = UICollectionViewCompositionalLayoutConfiguration()
        config.interSectionSpacing = 0
        config.scrollDirection = .horizontal
        
        compositionalLayout.configuration = config
        
        super.init(collectionViewLayout: compositionalLayout)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.isPagingEnabled = true
        
        // Register cell for reuse
        collectionView.register(CarouselCell.self, forCellWithReuseIdentifier: CarouselCell.reuseIdentifier)
    }
    
    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.images.count
    }
    
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let reusableCell = collectionView.dequeueReusableCell(withReuseIdentifier: CarouselCell.reuseIdentifier, for: indexPath) as? CarouselCell else {
            fatalError()
        }
        
        let index : Int = (indexPath.section * self.images.count) + indexPath.row
        
        reusableCell.configure(with: self.images[index])
        
        return reusableCell
    }

}

Notes

I found a similar unanswered question here. I'm sure something can be done about it because if I switch to SwiftUI with a TabView, that according to SwiftUI Introspect documentation for TabViewWithPageStyleType, is using UICollectionView under the hood, I'm not getting that ugly animation anymore. Though I can't switch to SwiftUI to use TabView because on interface rotation it loses the page index (well known bug, see here), which probably is even trickier to workaround.

Full width UICollectionViewCells overlap during interface orientation rotations causing ugly animation
 
 
Q