Reputation: 164
I'm trying to place a UICollectionView
in a UITableViewCell
. I want to display comments in the UICollectionViewCell
s 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.
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
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:
DispatchQueue.main.async {
self.journalsCollectionViewHeightConstraint.constant = self.columnLayout.itemSize.height
NotificationCenter.default.post(name: Notification.Name("updateJournalsTableViewHeight"), object: nil, userInfo: nil)
}
Hope this helps or gives you some ideas.
Upvotes: 0
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
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