I am trying to build collectionview with textview inside collectionviewcell.I want to calculate height of cell based on textview.
I use sizeThatFits method to calculate the height but as the text grow bigger, the height of collectionviewcell does not match with text.
I tried to use boundingRect but the result was also incorrect
Is their anyway to calculate correct height of textview? or how to solve this problem.
Thank you!!
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let textview = UITextView()
textview.text = statusText[indexPath.row]
let actualsize = textview.sizeThatFits(CGSize(width: collectionView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
return CGSize(width: collectionView.frame.size.width, height: actualsize.height)
}
By default, when you create a new instance of UITextView the font attribute is nil, you must associate a font to UITextView before use sizeThatFit.
If you are using the default font, just add textview.font = UIFont.systemFont(ofSize: 14)
Change your function like this :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let textview = UITextView()
textview.text = statusText[indexPath.row]
textview.font = UIFont.systemFont(ofSize: 14)
let actualsize = textview.sizeThatFits(CGSize(width: collectionView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
return CGSize(width: collectionView.frame.size.width, height: actualsize.height)
}
Related
I have a collection view with a section header that displays a users information. The header can include a bio from the user. I am having trouble with resizing the header if the bio is long. The label will not display the whole bio because the header stays at a fixed height.
I created the collection view via storyboard however I added the constraints programmatically.
Here is the bio constraints, I thought by setting the height and lines to 0 I would be okay,
addSubview(bioLabel)
bioLabel.anchor(top: editProfileButton.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 12, paddingLeft: 12, paddingBottom: 0, paddingRight: 12, width: 0, height: 0)
I also thought I could override the storyboard by
// Trying to override storyboard header height so the header can strech depending on the bio height
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
//TODO change height to be bounds.height of bioLabel?
return .init(width: view.frame.width, height: 300)
}
but it appears even if I try to change the size through code, it keeps the storyboard height of 225 ( which is what I would like)
None of the answers given so far are good practices since calculating the size manually or by rendering a label offscreen are both inefficient and violate the single source of truth principle.
Instances of UICollectionReusableView have the preferredLayoutAttributesFitting() method. When the cell becomes displayed, this method will be called. In this method, if you use Auto Layout, you ask the whole cell for its systemLayoutSizeFitting() and then modify the attributes. The layout will then be responsible to apply them via layout invalidation.
Use an instance of UICollectionReusableView to be your header.
And calculate the actual size of header in this method
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { }
Make sure you header is ready for dynamic height as well. Good luck.
You can get Label Height using below function
func heightForLabel(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
}
Collection Header Method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let height = self.heightForLabel(text: 'your text', font: 'font which you in label' , width: 'your label width')
return .init(width: view.frame.width, height: height+10) //10 is extra height if you want or you can remove it.
}
This function will do the work. Just pass the label text
func estimatedLabelHeight(text: String) -> CGFloat {
let size = CGSize(width: view.frame.width - 16, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10)]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
And use it like
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
//TODO change height to be bounds.height of bioLabel?
return .init(width: view.frame.width, height: estimatedLabelHeight(text: "Text that your label is gonna show"))
}
You need to calculate the size of cell height in method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
var size = CGSize()
let cell = YourUICollectionViewCell()
cell.textLabel?.text = data?[indexPath.row].text
let fitting = CGSize(width: cell.frame.size.width, height: 1)
size = cell.contentView.systemLayoutSizeFitting(fitting,
withHorizontalFittingPriority: .required,
verticalFittingPriority: UILayoutPriority(1))
return size.height //or just return size
}
If UILabel is custom then you need to set the bottom Constraint of UILabel to view with less than 1000 priority.
Hope it helps
I have a horizontal collection view.
Each cell has a label and an image.
The problem that I am facing is that when label renders data from the server, sometimes the text is too big and overlaps the image because the width of each cell is fixed.
How can I change the width of the collection View view cell according to the data it contains so that the label does not overlap the image?
CollectionView does have a delegate method which returns cell size.
Please inherit UICollectionViewDelegateFlowLayout and implement the following method.
func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 30, height: 30)
}
You can calculate your label width and return it to the function.
Hope this will help.
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 {
// Note: Margins are spaces around the cell
// model[indexPath.row].text is the text of label to be shown
let newWidth = model[indexPath.row].text.size(with: labelFont!).width + margins + imageView.width
return CGSize(width: newWidth, height: collectionView.bounds.height)
}
Note:
Don't forgot to confirm your viewController to UICollectionViewDataSource, UICollectionViewDelegateFlowLayout.
Guys I want to change the size of the cell like this
Auto adjusting to the text. what I am able to achieve is
I have tried to search but not able to find out the solution. How can I make it dynamic to text.
The tags are coming from a model class and the colour and background is changing from custom class for the cell.
Its explained in the answer below.
HorizontalCollectionView Content width and spacing
Calculate size of string with associate font.
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)
}
Check the code below:-
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let btn = UIButton()
btn.setTitle(?*self.arr[indexPath.row].name, for: .normal)
let btnWidth = ?*btn.intrinsicContentSize.width > UIScreen.main.bounds.width - 32 ? ?*btn.intrinsicContentSize.width - 32 : ?*btn.intrinsicContentSize.width
return CGSize(width: (?*btnWidth), height: 48)
}
You can modify it according to your requirement. Comment if you need any help in this.
Hope it helps :)
Im currently attempting to calculate the height of a UICollectionViewCell dynamically but using a UILabel (the dynamic property) and the current height of the cell. The height is calculated by getting height for sizeThatFits of the label within the cell and adding that value to the original height of the cell. For some reason this always returns 0 in sizeForItemAt. I'm pretty sure the problem is the size is being calculated before the cell is created thus will always return 0. Is there anyway around this? (The UI for the cell is created within the class itself, not at celllForItemAt could that also be a problem?)
var descHeight: CGFloat = 0
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "desc", for: indexPath) as! ImageDescCell
descHeight = cell.descriptionLbl.sizeThatFits(cell.frame.size).height + cell.bounds.height
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let w = collectionView.bounds.width
return CGSize(width: w, height: descHeight)
}
NSAttributedString can tell you how big an instance is, given a bound. So assuming you know how wide the label is and you know what the text is and what its attributes are you can get the size without ever creating a label:
let width = CGFloat(200)
let attributedString = NSAttributedString(string: "Test", attributes: [NSFontAttributeName : UIFont(name: "Helvetica", size: 18)!])
let boundingRect = attributedString.boundingRect(with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
swift-3.0
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
let width = self.collectionView.frame.size.width
let Height = self.view.frame.size.height - 64 // 64 is a navigation bar height minus.
let cellSize:CGSize = CGSize(width: width, height: Height )
return cellSize
}
I am trying to adjust the size of each collection view cell according to the length of the label text contained within
func collectionView(collectionView: UICollectionView, layout collectionViewLayout:UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var size = CGSize()
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("lessonCell", forIndexPath: indexPath) as UICollectionViewCell
var label: UILabel = cell.viewWithTag(300) as UILabel
var labelSize = label.frame.size
size = labelSize
return size
}
When running the code, the app crashes with the error 'negative or zero sizes are not supported in the flow layout.' However, when I stepped through, I found that the crash occurs in initializing the cell variable, before the size is even determined. Why would initializing my cell variable throw this type of error?
I found my problem. I was using collectionView.dequeueReusableCellWithReuseIdentifier() when in reality this should only be used with the "cellForItemAtIndexPath" delegate method. What worked for me was the following code:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var size = CGSize(width: 0, height: 0)
var label = UILabel()
label.text = category[indexPath.row]
label.sizeToFit()
var width = label.frame.width
size = CGSize(width: (width+20), height: 50)
return size
}