Search code examples
iosswiftuicollectionviewuicollectionviewflowlayoutscroll-paging

UICollectionView horizontal paging not centring after rotation


I am currently working on a horizontal gallery scroll view. I have decided to use UICollectionView with horizontal paging enabled. It almost works great... expect on rotation the collection view cells are not centred (see screenshot attached), where red is the first cell and yellow is the second one.

enter image description here

It usually works fine on the first rotation but then just messes up :D

This is the code I have for setting up the class:

class GalleryScrollView: UIView {
    private var collectionView: UICollectionView!
    private var layout: UICollectionViewFlowLayout!
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    override func layoutSubviews() {
        layout.invalidateLayout()
    }

    private func setupView() {
        layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        collectionView = UICollectionView(frame: self.frame,
                                          collectionViewLayout: layout)
        collectionView.register(UINib(nibName: "PhotoGalleryCell", bundle: nil), forCellWithReuseIdentifier: "photoGalleryCell")
        collectionView.isPagingEnabled = true
        collectionView.contentInsetAdjustmentBehavior = .never
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.autoresizingMask = .flexibleWidth
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.backgroundColor = .green
        self.addSubview(collectionView)

        NSLayoutConstraint.activate([
            collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            collectionView.topAnchor.constraint(equalTo: self.topAnchor),
            collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        ])

        collectionView.reloadData()
    }
}

Followed by the collection view setup:

extension GalleryScrollView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 0
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoGalleryCell", for: indexPath) as? PhotoGalleryCell else {
        return UICollectionViewCell()
    }

    if indexPath.item == 0 {
        cell.backgroundColor = .red
    } else {
        cell.backgroundColor = .yellow
    }

    return cell
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    2
}

func collectionView(_ collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                    sizeForItemAt indexPath: IndexPath) -> CGSize {
    return self.frame.size
}

I tried playing around with func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint { but for some reason the func was never called.

Did anyone else run into this problem? I saw a lot of topics in objective c threads, not much in swift. I am also somewhat struggling to understand why this is happening, so just understanding the problem would be very helpful. Thank you!


Solution

  • You don't seem to have handled rotations. You need to reloadData when a rotation occurs. You'll get the event in UIViewController from there you can call reloadData on the instance of your UICollectionView.

    class GalleryViewController: UIViewController {
        //...
        var galleryScrollView: GalleryScrollView
        //...
        override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
            super.willTransition(to: newCollection, with: coordinator)
            galleryScrollView.collectionViewLayout.invalidateLayout()
        }
        //...
    }
    

    Then in GalleryScrollView:

    class GalleryScrollView: UIView {
        var currentVisibleIndexPath = IndexPath(row: 0, section: 0)
        //....
    }
    
    extension GalleryScrollView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
        //...
        func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
            let attributes =  collectionView.layoutAttributesForItem(at: currentVisibleIndexPath)
            let newOriginForOldIndex = attributes?.frame.origin
            return newOriginForOldIndex ?? proposedContentOffset
        }
    
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    
            let center = CGPoint(x: scrollView.contentOffset.x + (scrollView.frame.width / 2), y: (scrollView.frame.height / 2))
            if let indexPath = self.screenView.indexPathForItem(at: center) {
                currentVisibleIndexPath = indexPath
            }
        }
    }