collectionView.reloadsection not reloading cell

Viewed 1463

So I am using compositional layout to auto resize a cell. This is how I create my layout section. I then return it in UICollectionViewCompositionalLayout(sectionProvider:).

    }

private func whatsNewSection() -> NSCollectionLayoutSection {
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(80))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item])

    let section = NSCollectionLayoutSection(group: group)
    section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 24, bottom: 24, trailing: 24)
    section.boundarySupplementaryItems = [
        .init(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
    ]

    return section
}

So this section looks something like this.

enter image description here

Now I have 2 nib files which each have there own class. The section header has only a label and a button. Lets ignore the header cause the problem lies with the WhatsNewCell. The class file looks something like this.

class WhatsNewCell: UICollectionViewCell {
  static let reuseIdentifier  = String(describing: WhatsNewCell.self)
  var delegate: WhatsNewCellDelegate?

  @IBOutlet weak var appVersionLabel: UILabel!
  @IBOutlet weak var appUpdateLabel: UILabel!
  @IBOutlet weak var moreButton: UIButton!


  override func awakeFromNib() {
    super.awakeFromNib()
    moreButton.addTarget(self, action: #selector(handleMore), for: .touchUpInside)
  }

  @objc private func handleMore() {
    self.moreButton.setTitle("", for: .normal)
    self.appUpdateLabel.numberOfLines = 0

    delegate?.moreButtonClick()
  }
}

Okay so the version label & the update label are in a stack view. When the user clicks the button I change the number of lines for the update label. Now I created a delegate called WhatsNewCellDelegate to be able to reload my collection view from my controller. This is how I reload my collection view in my controller. Also I did set the cell delegate to my controller when I init a cell.

extension AppDetailCollectionVC: WhatsNewCellDelegate {
func moreButtonClick() {
   //collectionView.collectionViewLayout.invalidateLayout()

    collectionView.performBatchUpdates({
        //collectionView.reloadData()
        collectionView.reloadSections(IndexSet(integer: 2))
    }, completion: nil)
}

}

Now it works when I call reload data but it doesn't work when I call reload sections. I have to press the more button twice for it to work with the reload sections. Also I tried to use invalidateLayout() too but it makes some of my cells disappear. Take a look at the screen shots below.

enter image description here

This is how it looks when I click the more button using invalidateLayout().

enter image description here

So I don't know what is going on. How would I be able to reload the section without having to press the more button twice. Would really appreciate the help.

4 Answers

Try running it all on main thread without performBatchUpdates?

DispatchQueue.main.async {
    collectionView.reloadSections(IndexSet(integer: 2))
}

So I got it to work. I had to add a property to my model. Something like this.

var shouldShowFullDetails: Bool = false

Then I had to do something like this.

func moreButtonClick() {
    model?.shouldShowFullDetails = true

    self.collectionView.reloadItems(at: [IndexPath(item: 0, section: 1)])
}

Then in my cell file I had to do something like this.

  class WhatsNewCell: UICollectionViewCell {
  static let reuseIdentifier  = String(describing: WhatsNewCell.self)
  var delegate: WhatsNewCellDelegate?

  @IBOutlet weak var appVersionLabel: UILabel!
  @IBOutlet weak var appUpdateLabel: UILabel!
  @IBOutlet weak var moreButton: UIButton!

  var model: Model? {
    didSet {
        configureCell()
    }
  }

  override func awakeFromNib() {
    super.awakeFromNib()

    moreButton.addTarget(self, action: #selector(handleMore), for: .touchUpInside)
  }

  @objc func handleMore() {
    delegate?.moreButtonClick()
  }

  private func configureCell() {
    guard let model = model else { return }

    if app.shouldShowFullReleaseNotes {
        appUpdateLabel.numberOfLines = 0
    } else {
        appUpdateLabel.numberOfLines = 3
      }
  }
}

What happens if you just do that?

extension AppDetailCollectionVC: WhatsNewCellDelegate {
    func moreButtonClick() {
         collectionView.collectionViewLayout.invalidateLayout()
    }
}

I suppose when you reloading section for the first time, collectionView creates new Cell and self.appUpdateLabel.numberOfLines takes initial value 0. You can add breakpoint in awakeFromNib and tap more button to check it.

Try add in method @objc private func handleMore() – self.invalidateIntrinsicContentSize()

And as Luis Ramirez mentioned, you should use some external configuration for cell.

Or if in this collection view only one cell with type WhatsNewCell, you can do something like this:

class WhatsNewCell: UICollectionViewCell {
    private static var shouldShowDetails = false


  override func awakeFromNib() {
    super.awakeFromNib()
    if Self.shouldShowDetails {
        self.moreButton.setTitle("", for: .normal)
        self.appUpdateLabel.numberOfLines = 0
    } else {
        self.moreButton.addTarget(self, action: #selector(handleMore), for: .touchUpInside)
    }
  }

    @objc private func handleMore() {
        Self.shouldShowDetails = true
        ....
    }
}
Related