Swift CollectionViewCell Auto Height - ios

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

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
}
}

iOS - dynamically size width of a UICollectionViewCell

I am just trying to dynamically size cells. I followed a few SO Questions and cannot seem to produce the same results.
Example one
Example two
My code right now is:
extension ItemViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = allTags[indexPath.item]
tagName.text = text //label from my custom cell (xCode not recognizing)
return CGRect(width: tagName.intrinsicContentSize.width + 60, height: 40)
}
}
In the second line, tagName is not identified by Xcode. It is part of my custom cell class. The first SO question I linked somehow was able to identify their cell properties within sizeForItemAt. How would I do that to dynamically size my cells? I do have estimated size to non-nil:
if let collectionViewLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
My desired result in similar to the photos. Let me know if you need more info. Thank you.
Try with This Way.
it's working fine for me in Swift5.
First Implement Collectionview FlowLayout Delegate
extension ItemViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = "Your Lable Text"
let font: UIFont = UIFont(name: "Font Name", size: 20) ?? UIFont.systemFont(ofSize: 17.0) // set here font name and font size
let width = text.SizeOf(font).width
return CGSize(width: width + 20.0 , height: 40.0) // ADD width + space between text (for ex : 20.0)
}
}
Add One String Extension.
extension String {
func SizeOf(_ font: UIFont) -> CGSize {
return self.size(withAttributes: [NSAttributedString.Key.font: font])
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UILabel.textWidth(font: UIFont(name: "pass same font name used in label", size: 12.0) ?? UIFont.systemFont(ofSize:
12.0), text: dataArray(indexPath.item))
return CGSize(width: width + 20, height: 50)
}
extension UILabel {
class func textWidth(font: UIFont, text: String) -> CGFloat {
let myText = text as NSString
let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(labelSize.width)
}
}

cellForItemAt not called if numberOfItemsInSection is 1 UICollectionView

Following is my code which is working fine if numberOfItemsInSection is greater than 1 but if numberOfItemsInSection is equal to 1 it should display single cell but it is not happening, for numberOfItemsInSection equals 1 noting is displayed and cellForItemAt is not called
Also initial numberOfItemsInSection is Zero after making api call and receiving data it becomes one and I call following code as well
tabsCollectionView.reloadData()
Other code
tabsCollectionView.delegate = self
tabsCollectionView.dataSource = self
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuResult?.foodMenuItems?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = tabsCollectionView.dequeueReusableCell(withReuseIdentifier: "foodTabsCollectionViewCellID", for: indexPath) as! FoodTabsCollectionViewCell
cell.foodMenuItem = menuResult?.foodMenuItems?[indexPath.row]
cell.setUI()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
following code related sizing in side viewDidLoad()
if let flowLayout = tabsCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 200, height: 70)
flowLayout.itemSize = UICollectionViewFlowLayout.automaticSize
tabsCollectionView.collectionViewLayout = flowLayout
}
sizeForItemAt method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height = collectionView.frame.height
var width = collectionView.frame.width
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
width = flowLayout.estimatedItemSize.width
height = flowLayout.estimatedItemSize.height
}
return CGSize(width: width, height: height)
}
Following is code inside FoodTabsCollectionViewCell
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.width = ceil(size.width)
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
layoutIfNeeded()
return layoutAttributes
}
I had the same problem when I tried to set a flexible width to a cell containing a UILabel with variable width (actually the size was related to the width of the text of the UILabel)
So initially I was using this approach (THIS was causing the problems):
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize <--- THIS
layout.itemSize.height = 70 <-- THIS
Then I removed the automaticSize (and itemSize.height) as below:
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
And it worked.
To make sure that I had flexible cell width I used this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let dummyCell = MyCell(frame: .init(x: 0, y: 0, width: frame.width, height: 40))
dummyCell.name = users[indexPath.item].name
// 3. Force the view to update its layout
dummyCell.layoutIfNeeded()
// 4. Calculate the estimated size
let estimatedSize = dummyCell.systemLayoutSizeFitting(.init(width: frame.width, height: 40))
return CGSize(width: estimatedSize.width, height: 40)
}
The problem will happen when the collectionView scroll direction is horizontal and the height of collectionView is less than 50. I had the same problem and I increased the collectionView height to 50 then the problem solved.

issue while set dynamic width of collection view cell

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)
}

Cell content disappears after set CellForItemAt indexPath size in CollectionViewFlowLayout?

I'm facing a weird problem. I'm using collectionViewFlowLayout sizeForItem to set cell size based on cell label width and somehow, I'm able to get the correct cell size but my content inside cell disappears. If I just put let say
CGSize(width: 30, height: frame.height)
I will see my label text. My collectionView scrollDirection is .horizontal
Below is my code:
let arrButtons = ["#B1","#ButtonButton2","#Bu3","#Buttontton11","#Buttodasfdsafasdn1","CongViecGiaDinh", "TinCongGiao"]
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let myText = arrButtons[indexPath.item]
let font = UIFont(name: HelveticaNeueFont.HelveticaNeueRegular.rawValue, size: 16)
return CGSize(width: myText.widthOfString(usingFont: font!), height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell
cell.data = arrButtons[indexPath.item]
cell.titleLabel.text = arrButtons[indexPath.item]
}
extension String {
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedStringKey.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}
//extension is used to calculate label width
It turns out that I used frame.height instead of collectionView.frame.height. Problem solved!

Resources