Sercan
Sercan

Reputation: 164

How to resize a horizontal UICollectionView height based on its content in a UITableViewCell

I'm trying to place a UICollectionView in a UITableViewCell. I want to display comments in the UICollectionViewCells with paging. There might be more than one line in comments so I want to resize the UICollectionView if there are more than one line in comment label.

This is the demo project for the question. I would really appreciate it if you can fix the problem on this demo.

The cells will look like this.

enter image description here

I tried some answers but I couldn't achieve any progress. Can you tell me what I need to do step by step? Demo would be much better if you can share. Thank you.

Upvotes: 10

Views: 2809

Answers (3)

mtl
mtl

Reputation: 71

My app has a horizontal collection view embedded in a table view cell which is all contained within a view controller. I was trying to achieve something similar to make sure that both the collection view cells and table view cells are dynamic height. I am also using a custom table view class/xib.

The steps I took to get the desired result:

  • Set a height constraint to the collection view in my table view cell xib
  • Create an IBOutlet for the constraint in the accompanying table view cell class
  • In awakeFromNib() of the table view cell class I have the following code snippet which sets the height constraint to the size of the items in my custom UICollectionViewFlowLayout
DispatchQueue.main.async {
  self.journalsCollectionViewHeightConstraint.constant = self.columnLayout.itemSize.height
  NotificationCenter.default.post(name: Notification.Name("updateJournalsTableViewHeight"), object: nil, userInfo: nil)
}
  • As you can see, I also post a notification. I listen for this notification in the parent view controller that holds the table view. This notification executes a function to reload the table view (the height for each row is set to UITableView.automaticDimension).

Hope this helps or gives you some ideas.

Upvotes: 0

Harsh
Harsh

Reputation: 2908

OK.. So I had the same problem... I fixed it with the following steps...

Create a height constraint outlet of the collection view, which you will set based on the dynamic content you have.

`@IBOutlet private(set) weak var collectionViewHeightConstraint: NSLayoutConstraint!`

Find the largest content model (which in your case would be the largest comment) and find the height of the largest collection view cell. This can be done with the following method.

private func getSizingCellHeight(for item: T.YourModel) -> CGFloat {
    guard let sizingCell = sizingCell else {
        return 0
    }

    sizingCell.frame.size.width = collectionViewItemWidth // This would be the width your collection view has
    sizingCell.prepareForReuse()

    // This is the method inside of your collection view cell, which lays out the content of the cell 
    sizingCell.configure(with: item)
    sizingCell.layoutIfNeeded()

    var fittingSize = UILayoutFittingCompressedSize
    fittingSize.width = collectionViewItemWidth

    let size = sizingCell.contentView.systemLayoutSizeFitting(
        fittingSize,
        withHorizontalFittingPriority: .required,
        verticalFittingPriority: .defaultLow
    )

    return size.height
}

Running the above method will give you the height of the largest cell, which you can use to the height constraint of the collectionView.

private func setCollectionViewHeight() {
    var largestHeight: CGFloat = 0

    //items would be your datasource for the collection view
    let items: [YourModel] = .... with some values
    if let items = items {
        for item in items {
            let height = getSizingCellHeight(for: item)
            if height > largestHeight {
                largestHeight = height
            }
        }
    }

    collectionViewHeightConstraint?.constant = largestHeight
}

This is actual working code which I have in my project.

From what I last remember, I followed Charlie's Article.

The Article also has a working demo on Github

Upvotes: 5

ISS
ISS

Reputation: 416

For dynamic cell sizing based on the length of your content you basically have to do some approximations. So here is my solution. It involves a couple of trial and error to get the calculations correct. Please bear with me now!

override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    if let user = datasource?.item(indexPath) as? User {
        let approximateWidthBioTextView = view.frame.width - 10 - 60 - 10
        let size = CGSize(width: approximateWidthBioTextView, height: 1000)
        let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]

        let estimatedFrame = NSString(string: user.bio).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)

        return CGSize(width: view.frame.width, height: estimatedFrame.height + 73)
    }

    return CGSize(width: view.frame.width, height: 200)
}

Essentially you need to change the sizeForItem func and do some approximations. My content is determined by instances of a User Class. So make sure before you start to get reference to the content you have inside your cell. In my case, it's a users that needs to be displayed. In a few words, my solutions get's the cell's height based on user.bioText. Because this is the only element that changes size in my cell. So bear this in mind.

First, you must compute the collectionView with. In my case, it is frame's width minus margins (10, 60 and 10). Remember, it is important to make this right.

Second, in order to estimate the cell's height we need to wrap it in a rect that has some attributes that needs to be filled out. In the size constant we deliberately give a high value so the system takes what it really needs. Now for the attributes if your content is a string as in my case, make sure you have the same font size.

Last, in the return section return CGSize(width: view.frame.width, height: estimatedFrame.height + 73), we still have to make some corrections, because text has some internal padding if I can say so, and unless you correct that, your cell hight won't display right. Make sure you play with the last value estimatedFrame.height + 73, in my case 73, until you get to the point where your cells hight are dynamically allocated perfectly. That's the only way. It sure works for me.

BTW, don't bother if your code looks a bit different(not too different tho). I am using a framework, but the calculation principles are the same. If you are asking yourself, where does the 73 value comes form, well that's because on the top of the bio text I need to display in each cell, I have the User's Name and UserName's labels that have both a eight of 20, making 40 in total and 33 is the gap or margins between them. As long as you give it the minimum value, it should work. It doesn't really affect the result if you give it a too high value. So experiment!

Upvotes: -1

Related Questions