1

I'm working with compositional layout collection views and need to support back to iOS 13, but I'm running into a strange issue when I update the content inset of the collection view. I'm using estimated cell sizes because eventually, these cells will all house different content, so they need to be dynamic.

You can see the issue here below in the video. When I press "Update" I set the bottom content inset to be 100. You can see the content jumps. Then when I start to scroll to the top, as I'm nearly there, the content jumps again to a different position in the collection view.

enter image description here

This isn't an issue on iOS 15 or 16. On iOS 14, the content doesn't jump when I update the content insets, but it does jump to a different position in the collection view when I start scrolling after it's been updated.

Here's my code below:

Main view controller

enum Section {
    case main
}

class ViewController: CollectionViewController {
    
    private lazy var collectionViewDataSource: UICollectionViewDiffableDataSource<Section, Int> = {
        let dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: collectionView) { [weak self] collectionView, indexPath, value in
            let cell = self?.cell(with: CollectionViewCell.self, for: indexPath, in: collectionView)
            cell?.configure(with: value)
            return cell
        }
        return dataSource
    }()
    
    private lazy var collectionView: UICollectionView = {
        let collectionViewLayout = configureChatCollectionViewLayout()
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        registerCell(CollectionViewCell.self, in: collectionView)
        return collectionView
    }()
    
    private lazy var updateButton: UIButton = {
        let button = UIButton()
        button.setTitle("Update", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(updateTapped), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.heightAnchor.constraint(equalToConstant: 44)
        ])
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
        setDataItems(Array(0..<100))
    }
    
    // MARK: - Interface methods
    
    func setDataItems(_ values: [Int]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
        snapshot.appendSections([.main])
        snapshot.appendItems(values)
        collectionViewDataSource.apply(snapshot, animatingDifferences: true)
    }
    
    // MARK: - Private methods
    
    func setupView() {
        view.backgroundColor = .white
        view.addSubview(updateButton)
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            updateButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            updateButton.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            updateButton.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.topAnchor.constraint(equalTo: updateButton.bottomAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
    }
    
    func configureChatCollectionViewLayout() -> UICollectionViewLayout {
        let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(1000))
        let item = NSCollectionLayoutItem(layoutSize: size)
        let group = NSCollectionLayoutGroup.vertical(layoutSize: size, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = 16
        return UICollectionViewCompositionalLayout(section: section)
    }
    
    @objc func updateTapped() {
        collectionView.contentInset = .init(top: 0, left: 0, bottom: 100, right: 0)
    }
    
}

Cell

class CollectionViewCell: UICollectionViewCell {
    
    private lazy var textLabel: UILabel = {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .title2)
        label.textColor = .black
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override func prepareForReuse() {
        super.prepareForReuse()
        textLabel.text = nil
    }
    
    func configure(with value: Int) {
        contentView.backgroundColor = .green
        textLabel.text = "\(value)"
        contentView.addSubview(textLabel)
        NSLayoutConstraint.activate([
            textLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
            textLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            textLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            textLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        ])
    }
    
}

UICollectionView helpers

class CollectionViewController: UIViewController {
    
    func registerCell<T: UICollectionViewCell>(_ type: T.Type, in collectionView: UICollectionView) {
        collectionView.register(type, forCellWithReuseIdentifier: type.computedReuseIdentifier)
    }
    
    func cell<T: UICollectionViewCell>(with type: T.Type, for indexPath: IndexPath, in collectionView: UICollectionView) -> T {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type.computedReuseIdentifier, for: indexPath) as? T else {
            assertionFailure("UICollectionViewCell cannot be cast to the intended type")
            return T()
        }
        return cell
    }
    
}

extension UICollectionViewCell {

    class var computedReuseIdentifier : String { "\(self)" }

}

1 Answer 1

0

I cannot reproduce this issue as I don't have a device that runs iOS 14. However, one idea comes to mind that might help with your case is to provide a more realistic height for your estimated cell size. I noticed you provided 1000 pts as your estimated cell height whereas your cells appear to be approximately 25 points in height. This big mismatch might be throwing Apple's scrollable layout calculation heuristics off.

Not the answer you're looking for? Browse other questions tagged or ask your own question.