issue while set dynamic width of collection view cell - ios

I am trying to dynamically set the width of collection view cell. Initially it's not rendering as expected. But when I tap on the cell, its getting adjusted as I want. Here's the code that I wrote:
Code
import UIKit
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var collView: UICollectionView!
var tasksArray = ["To Do", "SHOPPING","WORK"]
var selectedIndex = Int()
override func viewDidLoad() {
super.viewDidLoad()
let layout = collView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = UICollectionViewFlowLayout.automaticSize
layout.estimatedItemSize = CGSize(width: 93, height: 40)
// Do any additional setup after loading the view, typically from a nib.
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tasksArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.lblName.text = tasksArray[indexPath.row]
if selectedIndex == indexPath.row
{
cell.backgroundColor = UIColor.lightGray
}
else
{
cell.backgroundColor = UIColor.white
}
cell.layer.borderWidth = 1
cell.layer.cornerRadius = cell.frame.height / 2
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath.row
self.collView.reloadData()
}
}
here i am attaching two image before tapping and after tapping so you can easily understood
[![Here is the image before i tap
on cell]2]2
so please tell me whats wrong in my code

Inside your CollectionViewCell override preferredLayoutAttributesFitting function This is where the cell has a chance to indicate its preferred attributes, including size, which we calculate using auto layout.
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.width = ceil(size.width)
layoutAttributes.frame = frame
return layoutAttributes
}

I have found a small trick for swift 4.2
For dynamic width & fixed height:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let label = UILabel(frame: CGRect.zero)
label.text = textArray[indexPath.item]
label.sizeToFit()
return CGSize(width: label.frame.width, height: 32)
}

It is obvious that you have to use sizeForItemAt flow layout delegate in order to pass the dynamic width. But the tricky part is to calculate the width of the cell based on the text. You can actually calculate the width of a text given that you have a font.
Let's introduce few extension which will help us along the way
StringExtensions.swift
extension String {
public func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect,
options: .usesLineFragmentOrigin,
attributes: [.font: font], context: nil)
return ceil(boundingBox.width)
}
}
This method let us know the width of a string, if i provide it the height and the font. Then use it inside sizeForItem as follows
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = 40
let text = YOUR_TEXT
let width = text.width(withConstrainedHeight: height, font: Font.regular.withSize(.extraSmall)) + EXTRA_SPACES_FOR_LEFT_RIGHT_PADDING
return CGSize(width: width, height: height)
}

Related

All CollectionViewCell height equal to largest cell label dynamic content

I need to create a vertical CollectionView which have a dynamic label and all CollectionViewCell height should be same to the largest cell content. How to calculate largest cell height and assign that height to every cell?
I have tried many solution but none is working for me.
Any solution pls
Thanks in advance.
Start by adding the following UILabel Extension (add anywhere outside of the ViewController):
extension UILabel{
public var getHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
Then you can calculate the height for each label while the collection view cell is rendering. In the following, I check to see if the new height is greater then the last tallest height - this is saved to my variable called tallestCellHeight. Use this variable to set the height of the cell.
var tallestCellHeight: CGFloat = 0
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "testCell", for: indexPath) as! testCell
cell.testLabel.text = messages[indexPath.row]
let currentCellHeight = cell.testLabel.getHeight
if currentCellHeight > tallestCellHeight {
tallestCellHeight = currentCellHeight
print(tallestCellHeight)
}
cell.backgroundColor = .tertiarySystemFill
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: 150, height: tallestCellHeight)
}
After the collection view has rendered, I will have the tallest cell height saved to the variable tallestCellHeight.
Lastly, reload the collection view on the main thread in viewDidLoad()
DispatchQueue.main.async {
self.collectionView.reloadData()
}
Don't forget to include UICollectionViewDelegate, UICollectionViewDataSource and UICollectionViewDelegateFlowLayout
Entire Code
import UIKit
let messages: [String] = ["hello and welcome", "this is a really cool app", "this is awesome", "welcome to the test for my new application", "hello everyone"]
class testCell: UICollectionViewCell {
#IBOutlet weak var testLabel: UILabel!
}
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
var tallestCellHeight: CGFloat = 0
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "testCell", for: indexPath) as! testCell
cell.testLabel.text = messages[indexPath.row]
let currentCellHeight = cell.testLabel.getHeight
if currentCellHeight > tallestCellHeight {
tallestCellHeight = currentCellHeight
print(tallestCellHeight)
}
cell.backgroundColor = .tertiarySystemFill
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: 150, height: tallestCellHeight)
}
}
extension UILabel{
public var getHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}

How to match the horizontal collectionView Cell's width on it's content size

I know some related questions already there regarding this but I tried those before and still no luck.
Here is my problem on the following screenshot
My current screen shows a fixed cell size and it displays empty spaces for smaller contents and larger contents going over the cell with dots..
I wanted like below
it should match the width of the product category name content.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.catCollectionView{
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
DispatchQueue.main.async {
catCell.configureCell()
let catCountInt = self.catCountArray[indexPath.row]
catCell.catCountLabel.text = String(catCountInt)
catCell.catNameLabel.text = self.categoryNameArray[indexPath.row]
catCell.catCountLabel.sizeToFit()
catCell.catNameLabel.sizeToFit()
}
return catCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
catCell.catNameLabel.text = self.categoryNameArray[indexPath.item]
catCell.catNameLabel.sizeToFit()
let labelWidth = catCell.catNameLabel.frame.width + 10
print("frame width: \(labelWidth)")
return CGSize(width: labelWidth, height: 21)
}
}
Maybe I'm missing a simple thing here but I couldn't quite figure out at this moment. Please help me and sorry for my strange English.
Let's imagine you are using a simple UICollectionViewCell subclass with constraints set up correctly in storyboard (label is pinned to all four sides of its superview) like this:
class CategoryCell: UICollectionViewCell {
#IBOutlet var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
layer.borderWidth = 1
}
}
Then you can simply let auto layout determine the cells' sizes:
class CollectionViewController: UICollectionViewController {
let categories = [
"All Products",
"Fresh",
"Health & Beauty",
"Beverages",
"Home & life"
]
private var flowLayout: UICollectionViewFlowLayout? {
return collectionViewLayout as? UICollectionViewFlowLayout
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .white
flowLayout?.sectionInset = .init(top: 15, left: 15, bottom: 15, right: 15)
flowLayout?.sectionInsetReference = .fromSafeArea
flowLayout?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
DispatchQueue.main.async {
self.flowLayout?.invalidateLayout()
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoryCell", for: indexPath) as! CategoryCell
cell.nameLabel.text = categories[indexPath.row]
return cell
}
}
Result:
Instead of dequeuing another cell in collectionView(_:layout:sizeForItemAt:), you can simply calculate the width of categoryName using size(withAttributes:) on the categoryName, i.e.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = self.categoryNameArray[indexPath.row] {
let cellWidth = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:14.0)]).width + 10.0
return CGSize(width: cellWidth, height: 21.0)
}
in attributes, give whatever font you want the categoryName to be.

HorizontalCollectionView Content width and spacing

How can I make my horizontal collection view labels width to wrap the content width of the label and make them have equal spacing between each of them? Currently I have the collection view cell width as 100. If I increase the width to fit other labels, the shorter labels have more spacing between them. Any ideas will be appreciated.
Calculate the width of the label text first with the font associated with the text.
extension String {
func size(with font: UIFont) -> CGSize {
let fontAttribute = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttribute)
return size
}
}
Return the calculated width along with collectionView height in collectionView(_, collectionViewLayout:_, sizeForItemAt).
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let newWidth = titles[indexPath.row].size(with: labelFont!).width + 10 //Added 10 to make the label visibility very clear
return CGSize(width: newWidth, height: collectionView.bounds.height)
}
Entire source code:
class ViewController: UIViewController {
#IBOutlet weak var collection: UICollectionView!
let labelFont = UIFont(name: "Helvetica Neue", size: 18)
let titles = ["Hi", "Hello", "HorizontalCollectionView", "VerticalCollectionView"]
override func viewDidLoad() {
super.viewDidLoad()
collection.backgroundColor = UIColor(red: 68/255, green: 143/255, blue: 1, alpha: 1)
collection.register(UINib.init(nibName: "CustomCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CustomCollectionViewCell")
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return titles.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10 // Adjust the inter item space based on the requirement.
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let newWidth = titles[indexPath.row].size(with: labelFont!).width + 10 //Added 10 to make the label visibility very clear
return CGSize(width: newWidth, height: collectionView.bounds.height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath) as! CustomCollectionViewCell
cell.titleLabel.text = titles[indexPath.row]
cell.titleLabel.font = labelFont!
return cell
}
}
extension String {
func size(with font: UIFont) -> CGSize {
let fontAttribute = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttribute)
return size
}
}
Another Solution:
ViewController.swift
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var collView: UICollectionView!
var tasksArray = ["To Do", "SHOPPING","WORK"]
var selectedIndex = Int()
override func viewDidLoad() {
super.viewDidLoad()
collView.register(UINib.init(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
let layout = collView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = UICollectionViewFlowLayout.automaticSize
layout.estimatedItemSize = CGSize(width: 170, height: 50)
// Do any additional setup after loading the view, typically from a nib.
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tasksArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.lblName.text = tasksArray[indexPath.row]
if selectedIndex == indexPath.row
{
cell.backgroundColor = UIColor.lightGray
}
else
{
cell.backgroundColor = UIColor.white
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath.row
self.collView.reloadData()
}
}
Cell.swift
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var lblName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
layer.borderWidth = 1
layer.cornerRadius = bounds.height / 2
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.width = ceil(size.width)
layoutAttributes.frame = frame
return layoutAttributes
}
}
You have to manually calculate the width of each cell. To calculate the width, you actually calculate the width of the text residing in that cell.
use following
boundingRect(with:options:attributes:context:)
https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrect
to calculate the width of a given string. So after calculating the returning the size the cell should be wrapping the text.
Now come the second part, equal spacing between items. use interItemSpacing to define the spacing between each cell.

Swift CollectionViewCell Auto Height

I try to make my cell to auto resize with the content of a label.
My Cell contains two labels. The first label is always one line, the other can be multiline.
The problem is that the cell won't resize. This is my code :
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
CollectionViewHeight.constant = CGFloat(60 + 60 * commentaires.count)
return commentaires.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = commentCollectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! CustomCollectionViewCell
cell.contentCommentLabel.text = commentaires[indexPath.row]
cell.nameCommentLabel.text = commentairesNom[indexPath.row]
cell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// This is Just for example , for the scenario Step-I -> 1
let yourWidthOfLable = self.view.bounds.size.width
let font = UIFont(name: "System", size: 14.0)
var expectedHeight = heightForLable(text: commentaires[indexPath.row], font: font!, width:yourWidthOfLable )
print("expectedHeight : " + String(describing: expectedHeight))
return CGSize(width: view.frame.width, height: expectedHeight)
}
func heightForLable(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
You need to get height of UILabel programmatically according to font family, font size and width of UILabel and than after add this height in top UILabel's height. This is your total height of cell.
Usually i use this code to configure my CollectionView:
private func config_collection()
{
self.collection_view.delegate = self
self.collection_view.dataSource = self
self.collection_view.keyboardDismissMode = .interactive
self.collection_view.alwaysBounceVertical = true
self.collection_view.showsVerticalScrollIndicator = true
self.collection_view.showsHorizontalScrollIndicator = false
if let layout = self.collection_view.collectionViewLayout as? UICollectionViewFlowLayout
{
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 2
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 50)
}
}
Edit 1:
To resize the cell by estimate height i use this code:
func get_size_height_text(texto: String,
ui_view: UIView) -> CGFloat
{
let l : [NSAttributedStringKey : Any] = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17)]
let size = CGSize(width: ui_view.frame.width,
height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimateFrame = NSString(string: texto).boundingRect(with: size,
options: options,
attributes: l,
context: nil)
return estimateFrame.height
}
To use it, run this code:
let height = get_size_height_text(texto: label.text ?? "",
ui_view: self.view)
with this height, you need to increment the value with your preference to use it in your cell

UICollectionView with autosizing cell (estimatedSize) and sectionHeadersPinToVisibleBounds goes mental

Consider the following situation. I have an UICollectionView (inside UICollectionViewController), which looks almost the same as UITableView (the reason why I don't use UITalbeView is because I have non data views on layout, that I don't want to manage and mess with my IndexPath).
In order to achieve the autosizing cells I've set estimatedItemSize, something like that:
layout.estimatedItemSize = CGSize(width: self.view.bounds.size.width, height: 72)
Also, in my cell I have layout attributes:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
return layoutAttributes
}
So, by doing that I've got exact layout as UITableView with autosizing. And it works perfectly.
Now, I am trying to add the header and pin it on scrolling to the top of the section, like that:
layout.sectionHeadersPinToVisibleBounds = false
but layout goes into weird state, I have glitches all over the place, cells overlapping each other, and headers sometimes doesn't stick.
UPDATE:
The code of view controller and cell:
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
let layout = collectionView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.sectionHeadersPinToVisibleBounds = true
layout.estimatedItemSize = CGSize(width: collectionView?.bounds.size.width ?? 0, height: 36) // enables dynamic height
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CustomCell
cell.heightConstraint.constant = CGFloat(indexPath.row * 10 % 100) + 10 // Random constraint to make dynamic height work
return cell
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
return collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath)
}
class CustomCell : UICollectionViewCell {
let identifier = "CustomCell"
#IBOutlet weak var rectangle: UIView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
translatesAutoresizingMaskIntoConstraints = false
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
return layoutAttributes
}
Details of lagging in video: https://vimeo.com/203284395
Update from WWDC 2017:
My colleague was on WWDC 2017, and he asked one of the UIKit engineers about this issue. The engineer confirmed that this issue is known bug by Apple and there is no fix at that moment.
Use the UICollectionViewDelegateFlowLayout method.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
//Calculate your Dynamic Size Here for Each Cell at SpecificIndexPath.
//For Example You want your Cell Height to be dynamic with Respect to indexPath.row number
let cellWidth = collectionView?.bounds.size.width
//Now Simply return these CellWidth and Height, and you are Good to go.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCellIdentifier", for: indexPath)
//You Can Pass TextField As Well as i have considered UITextView for now.
let cellSize = self. calculateSize(cellTextView: cell.textView, cellText: yourArrayOfText[indexPath.row], withFixedWidth: cellWidth)
return cellSize
}
And do not change the Cell Height by changing the Constraint.constant directly. Instead of this simply use Above Delegate method to change height. Changing Cell Constraint can cause issues like this.
Use bellow method to get your desired Size.
func calculateSize(cellTextView: UITextView, cellText: String, withFixedWidth: CGFloat) -> CGSize {
let textView = UITextView()
textView.text = cellText
textView.frame = cellTextView.frame
textView.font = cellTextView.font
textView.tag = cellTextView.tag
let fixedWidth = withFixedWidth
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
return newSize
}
Use Bellow method to calculate the image size.
//Method to Calculate ImageSize.
func calculateImageSize(image: UIImage) -> CGSize {
var newSize = image.size
if newSize.width > (ChatView.maxWidth - 70) {
newSize.width = ChatView.maxWidth - 70
newSize.height = ChatView.maxWidth - 70
}
if newSize.height < 60 {
newSize.height = 60
newSize.width = 60
}
return newSize
}

Resources