0

I have created a horizontal scrolling collection view within a parent UIView, which I add in my UIViewController. But the scrolling view shifts up when I scroll on it.

This is how the container view is initialized:

 override init(frame: CGRect){
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
        layout.estimatedItemSize = CGSize(width: 100, height: 50)
        
        countryCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
        
        viewTitleLabel = UILabel()
        super.init(frame: frame)
        setupTableView()
        sortedDummyData = dummyData.sorted(by: <)
    }

Constraints on my child views:

 NSLayoutConstraint.activate([
            viewTitleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5),
            viewTitleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            viewTitleLabel.heightAnchor.constraint(equalToConstant: 30),
            viewTitleLabel.widthAnchor.constraint(equalToConstant: 90),
            
            countryCollectionView.leadingAnchor.constraint(equalTo: viewTitleLabel.trailingAnchor, constant: 10),
            countryCollectionView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            countryCollectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
            countryCollectionView.heightAnchor.constraint(equalToConstant: 50)
            
 ])

Constraints on container view when adding to UIViewContoller:

 countryCountView.translatesAutoresizingMaskIntoConstraints = false
 countryCountView.isUserInteractionEnabled = true
 view.addSubview(countryCountView)
        
 NSLayoutConstraint.activate([
      countryCountView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
      countryCountView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      countryCountView.topAnchor.constraint(equalTo: headerContainer.bottomAnchor),
      countryCountView.heightAnchor.constraint(equalToConstant: 60)
 ])

My UICollectionViewCell:

override init(frame: CGRect) {
        countryNameLabel = UILabel()
        countryUserCountLabel = UILabel()
        super.init(frame: frame)

        contentView.addSubview(countryNameLabel)
        countryNameLabel.translatesAutoresizingMaskIntoConstraints = false
        countryNameLabel.font = UIFont.boldSystemFont(ofSize: 13)
        countryNameLabel.textColor = .white
        
        contentView.addSubview(countryUserCountLabel)
        countryUserCountLabel.translatesAutoresizingMaskIntoConstraints = false
        countryUserCountLabel.font = UIFont.systemFont(ofSize: 13)
        countryUserCountLabel.textColor = .white
        
        NSLayoutConstraint.activate([
            countryNameLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5),
            countryNameLabel.heightAnchor.constraint(equalToConstant: 30),
            countryNameLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 5),
            countryNameLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5),
//            countryNameLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0),
//            countryNameLabel.widthAnchor.constraint(equalToConstant: 20),

            countryUserCountLabel.leadingAnchor.constraint(equalTo: countryNameLabel.trailingAnchor, constant: 5),
            countryUserCountLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 5),
            countryUserCountLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5),
            countryUserCountLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -5),
            countryUserCountLabel.heightAnchor.constraint(equalToConstant: 30),
//            countryUserCountLabel.widthAnchor.constraint(equalToConstant: 40)
        ])
    }

Height returned by delegate method:

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 70, height: 40)
//        return CGSize.zero
    }

Before scrolling:

After scrolling:

Edit:

Setting collectionView.showsHorizontalScrollIndicator = false does not help.

Also setting layout.estimatedItemSize = CGSize.zero fixes the shifting up but stops the cells from resizing correctly.

Parth
  • 2,682
  • 1
  • 20
  • 39

2 Answers2

0

Is it possible that a Scroll Indicator is present but you cant see it? Try this. collectionView.showsHorizontalScrollIndicator = false

If it doesn't work check view your hierarchy after it's moved up.

Waylan Sands
  • 321
  • 3
  • 11
  • I tried setting `collectionView.showsHorizontalScrollIndicator = false` but did not help. – Parth Jan 12 '21 at 10:07
  • Ok have a look at your view your hierarchy and see what's going on https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ExaminingtheViewHierarchy.html – Waylan Sands Jan 12 '21 at 10:10
  • https://stackoverflow.com/questions/56840665/why-on-xcode-11-uicollectionviewcell-changes-size-as-soon-as-you-scroll-i-alre – Waylan Sands Jan 12 '21 at 10:12
  • This stops the collection view from moving up but now the cell is not resizing. – Parth Jan 12 '21 at 10:16
0

Since setting layout.estimatedItemSize = CGSize.zero solves the layout shift issue but does not resize the cells accordingly, I'm guessing a possibility where you might have hardcoded the width constraint for the label used in each cell of your collection view. (Like the constraints from your code for viewTitleLabel)

Assuming that this is the case and you want auto-resizing to take place, you can try removing the hardcoded width constraints. (If any)

For auto-layout to do its magic, only the X and Y position constraints are usually needed for UILabel unless you need strict control over the boundaries

EDIT:

The problem seems to be with the way the cell constraints are set up. Here's the breakdown:

The shift-up occurs because:

  1. The cells are initially loaded with your collection view's height i.e: 50

  2. Since you've set layout.itemSize = UICollectionViewFlowLayout.automaticSize, the flow layout overrides the default height of 50 and sets the cell height according to its subview constraints setup.

  3. You've specified the label's heightAnchor to be 30, topAnchor to be 5, and the bottomAnchor to -5 making the cell's overall height to be 40 when calculating for autolayout

  4. This height switch from 50 to 40 causes your "shift-up" issue.

To work around this:

  • You can change the label's heightAnchor to be 50 to match with the collection view's height, set its centerYAnchor and maintain your layout.itemSize = UICollectionViewFlowLayout.automaticSize

                            (OR)
    
  • Set the label's centerYAnchor and manually specify layout.itemSize with a height of 50

Lokesh SN
  • 1,583
  • 7
  • 23
  • The `viewTitleLabel` is actually not part of the collection view. And for the cell contents, I have only set the height. – Parth Jan 12 '21 at 11:27
  • Yes, I understand. I mentioned `viewTitleLabel` because you might have followed the same practice. But now it's clear :) – Lokesh SN Jan 12 '21 at 11:28
  • @ParthTamane What's your cell subview hierarchy and setup? I can try mocking the same... – Lokesh SN Jan 12 '21 at 11:29
  • @ParthTamane Successfully reproduced your issue, I've edited my answer. – Lokesh SN Jan 12 '21 at 12:36
  • What will happen if I set the height everywhere to 40? I have this function too: ``` func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: 70, height: 40) // return CGSize.zero }``` – Parth Jan 12 '21 at 13:24
  • If you change the collection view's height to 40 too, you're good to go. But if you'd like to maintain the cv's height of 50, you might have to provide top and bottom padding to the cells to keep them centred and avoid the shift-up. You have many ways to get this right now that you know the issue ;) – Lokesh SN Jan 12 '21 at 14:02
  • Do I need the `sizeForItemAt` method? I feel that is also messing up stuff... – Parth Jan 12 '21 at 14:29
  • You don't necessarily need it. If you remove the method, the flow layout will use the value provided in `layout.itemSize` to decide the size. You could just remove the method, use the `itemSize` which holds `UICollectionViewFlowLayout.automaticSize` and correct your cell constraints so that it just gets handled automatically (if that's what you want) – Lokesh SN Jan 12 '21 at 14:45