3

I have to add a UICollectionView inside a UITableViewCell. The collectionView can have a different number of items. So the collectionView should adjust properly inside the tableView.

I have implemented this inside my Project: https://github.com/vishalwaka/DynamicCollectionViewInsideTableViewCell

In my case, the height of the cell is not being the adjusted after the height of the collectionView is set.

How can I set the collectionView to not be scrollable and also show all content inside the tableviewcell.

Kathiresan Murugan
  • 2,783
  • 3
  • 23
  • 44
vishalwaka
  • 89
  • 1
  • 12
  • Possible duplicate of [How to dynamically change the height of a UITableView Cell containing a UICollectionView cell in Swift?](https://stackoverflow.com/questions/45176747/how-to-dynamically-change-the-height-of-a-uitableview-cell-containing-a-uicollec?rq=1) – Sateesh Yemireddi Jan 04 '19 at 11:47
  • @Sateesh I have to use the collection view cells of different width, so I can not calculate how many rows will be there. – vishalwaka Jan 04 '19 at 12:40
  • please share the some sample code. – Sateesh Yemireddi Jan 04 '19 at 12:44
  • @Sateesh It is in the shared GitHub project. – vishalwaka Jan 04 '19 at 14:21
  • Have you tried [this](https://stackoverflow.com/questions/42437966/how-to-adjust-height-of-uicollectionview-to-be-the-height-of-the-content-size-of) ? – Chinh Nguyen Jan 07 '19 at 14:27

3 Answers3

7

Create a collectionView inside UITableViewCell with Leading, Trailing, Top, Bottom, Height constraint like this:

enter image description here

Now, in your tableViewCell class make a NSKeyValueObserver and add this on UICollectionView's contentSize property and assign contentSize changes to collectionView's height constraint.

class FormCollectionTableViewCell: UITableViewCell{

  var collectionViewObserver: NSKeyValueObservation?

  override func awakeFromNib() {
    super.awakeFromNib()
    addObserver()
  }

  override func layoutSubviews() {
        super.layoutSubviews()
        layoutIfNeeded()
  }

  func addObserver() {
       collectionViewObserver = collectionView.observe(\.contentSize, changeHandler: { [weak self] (collectionView, change) in
            self?.collectionView.invalidateIntrinsicContentSize()
            self?.collectionViewHrightConstarint.constant = collectionView.contentSize.height
            self?.layoutIfNeeded()
        })
    }
   deinit {
      collectionViewObserver = nil
   }
}

And in your viewController class use the following code:

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        tableView.reloadData()
}

Now, your table view will have collection view with dynamic height. Don't forgot to disable scrolling of UICollectionView and adjusting it's flow.

nikksindia
  • 1,147
  • 1
  • 10
  • 18
  • I am getting a constraint break when specifying the height in advance. And after changing the constraint to be of low priority, the collection view is not having proper height. Can you share your project? – vishalwaka Jan 07 '19 at 19:58
  • collectionView.contentSize.height is setting the height properly, but it is not expanding or shrinking the cell height after that. – vishalwaka Jan 07 '19 at 20:03
  • @vishalwaka: are you setting priority of height constraint equal to 1000 and implementing viewDidLayoutSubviews in your viewController class. For me it is working fine. – nikksindia Jan 08 '19 at 06:05
  • all the `layoutIfNeeded` are not really necessary and shouldn't be needed. – Sulthan Jan 08 '19 at 15:43
  • @nikksindia For a height constraint of 1000, the constraint will start breaking. I have implemented viewDidLayoutSubviews in my viewController class. I have updated the code at https://github.com/vishalwaka/DynamicCollectionViewInsideTableViewCell . Also there are sometimes views not updating properly. – vishalwaka Jan 08 '19 at 20:47
  • I have tried this as well and it's not working for me. Loading my data from Firebase Storage. – Luke Irvin Dec 18 '20 at 21:01
0

Create the following subclass of a collectionView:

class IntrinsicSizeCollectionView: UICollectionView {

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.setup()
}

override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
    super.init(frame: frame, collectionViewLayout: layout)

    self.setup()
}

override func layoutSubviews() {
    super.layoutSubviews()
    if !self.bounds.size.equalTo(self.intrinsicContentSize) {
        self.invalidateIntrinsicContentSize()
    }
}

override var intrinsicContentSize: CGSize {
    get {
        let intrinsicContentSize = self.contentSize
        return intrinsicContentSize
    }
}

func setup() {
    self.isScrollEnabled = false
    self.bounces = false
}

Add the collectionView to a tableViewCell and set its constraints leading, trailing, top, bottom to the bounds of the cell. Also set the intrinsic size as in the screenshot.

enter image description here

The size of the tableViewCell should set to the UITableViewAutomaticdimension and also an estimatedHeight for this cell should be set in the dataSource of the TableView.

That should be all.

Klinki
  • 368
  • 1
  • 11
  • Even after making these changes, I am getting the same issue. Sometimes the cell is not expanded as the content of the collection view changes. Also, the shrinking is not happening the properly. – vishalwaka Jan 07 '19 at 06:34
  • Can you share your project? – vishalwaka Jan 07 '19 at 19:55
0

After checking your project I suggest you use a slightly different approach.

After updating the model, you can ask the collection view layout what the new content size will be (collectionViewLayout.collectionViewContentSize). You then update the height constraint constant.

So, you should:

  1. update your model
  2. reload the row
  3. ask the layout for its new contentSize
  4. update the height constraint constant

This modification in your project should achieve this:

// Remove the content size observing logic.

func configureForVehicle(_ vehicle: Vehicle, selectedHouseType: String) {
    self.vehicle = vehicle

    applianceCollectionView.reloadData()

    // Set the height constraint to the new value, found in the layout's content size info.
    // Since the model (self.vehicle) was already updated, the layout will return the new content size.
    // Because this is all happening while the cell is being reloaded, the cell will also resize properly.
    // You don't need to perform any other setNeedsLayout/layoutIfNeeded calls.
    self.applianceCollectionViewHeightConstraint.constant = applianceCollectionView.collectionViewLayout.collectionViewContentSize.height
}

Note the comment about not needing any additional layout update calls. Only calling tableView.reloadRows(at: [indexPath], with: .automatic) should be enough. Unnecessary layout updates can cause a performance hit.


As a side note there is another approach you can use if you wish to animate the height change without reloading the cell. In this case you would need to keep/find a reference to the cell you wish to update then do the following:

  1. Update the model for the cell without calling any table view reload calls.
  2. Reload the collection view on the updated cell.
  3. Update the height constraint same way as above.
  4. Use a callback of some sort to notify your table view that a cell wishes to update its height.
  5. In the callback call tableView.beginUpdates(); tableView.endUpdates(). This causes the tableView to re-layout its cells and animate height changes.

From beginUpdates() documentation:

You can also use this method followed by the endUpdates() method to animate the change in the row heights without reloading the cell. This group of methods must conclude with an invocation of endUpdates().

teddy
  • 217
  • 1
  • 6
  • The solution you have suggested is not working for me. Can you check it once by updating the project on your machine? – vishalwaka Jan 14 '19 at 10:53
  • Your project works for me if I do the following: 1. Remove the `viewDidLayoutSubviews()` override from `ViewController` and `layoutSubviews()` override in `ApplianceTableViewCell` 2. Remove the content size observer in `ApplianceTableViewCell` 3. Implement the code from my answer in `ApplianceTableViewCell` – teddy Jan 14 '19 at 13:13
  • EDIT: After checking again I noticed the `applianceCollectionView.reloadData()` was necessary after all. Edited my answer. – teddy Jan 14 '19 at 13:24