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
}
Related
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 :)
I need to dynamically calculate the height of cell of a UICollectionView. I am using this function
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
The problem is that it returns the size of every cell before it is displayed. If there are 120 items in the array it will calculate 120 cell sizes before the
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
is called. This is creating big performance problems. It takes up 10 seconds to load the entire collection. If I dont use sizeforitematindexpath, the collection loads in 1 second. How can I solve this problem ?
I am using Xcode 8.3.3 & Swift 3.0
Here is my exact code for the first delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let json = JSON(mySources[indexPath.row])
var url = NSURL(string: json["cover"]["source"].stringValue)
var data = NSData(contentsOf: url as! URL)
var photo = UIImage(data: data as! Data)
var height1 = photo?.size.height
var boundingRect = CGRect(x:0, y:0, width: 372, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: (photo?.size)!, insideRect: boundingRect)
let imageHeight = rect.height
let font = UIFont(name: "Raleway-SemiBold", size: 17)
let titleHeight = heightForLabel(text: json["name"].stringValue, font: font!, width: 160)
let restaurantHeight = heightForLabel(text: json["place"]["name"].stringValue, font: font!, width: 160)
print(restaurantHeight + titleHeight + imageHeight)
return CGSize(width: 372, height:imageHeight + titleHeight + restaurantHeight + 100)
}
UICollectionViewFlowLayout always computes the size of all your cells at once before starting preparing the cells themselves.
However if I remember well you can prevent that by setting a non-zero value to its estimatedItemSize property (the documentation is not crystal clear about it):
The estimated size of cells in the collection view.
Providing an estimated cell size can improve the performance of the collection view when the cells adjust their size dynamically. Specifying an estimate value lets the collection view defer some of the calculations needed to determine the actual size of its content. Specifically, cells that are not onscreen are assumed to be the estimated height.
The default value of this property is CGSizeZero. Setting it to any other value causes the collection view to query each cell for its actual size using the cell’s preferredLayoutAttributesFitting(_:) method. If all of your cells are the same height, use the itemSize property, instead of this property, to specify the cell size instead.
So calling the next piece of code might resolve your performance issues:
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = CGSize(width: 375.0, height: 44.0)
You should use reusable cells so that your collection view doesn't load all data at once but only what's on the screen. Which is much less than the total. This will save performance, memory, and will lessen the CPU usage (smoother graphics, less battery consumption).
How to:
for your collectionView:
menuCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MenuCell")
in the function cellForItemAtIndexPath:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath as IndexPath)
return cell
}
Hope this helps!
I have a collectionView with horizontal UICollectionViewFlowLayout.
I am trying to achieve:
If a device orientation is portrait, UIImageView width will be qual to view.width and let the height be calculated automatically (like it usually happens with Auto Layout). And the same for the landscape mode. Example - standard photo app on the Iphone.
Unfortunately i don't see how to achieve it with autoLayout. I set constraints on UIImageView for it to be equal in size to the cell. But looks like the sell itself cannot be pinned to the Parent View.
After reading similar questions looks like cells must be resized programmatically using
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: width, height: height)
}
And here i am stuck because i know the width of the screen but don't know how to calculate the height dynamically.
About image height:
I have my image declared like this:
var pageImages = [UIImage]()
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: ImageDetailViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! ImageDetailViewCell
let curr = indexPath.row
let imgName = data[albumNumber][curr]["image"]
cell.DetailImageView.image = UIImage(named: imgName!)
return cell
}
If you use the proper UIImageView resize setting (say, aspect fit/fill), then you just need to set the cell's height to your collectionView's (you get a pointer to it as one of the ...sizeForItemAtIndexPath... method parameters) height. You also should call the - layoutIfNeeded method on your cell afterwards.
You can use sizeForItemAtIndexPath: to change the size of collection view cell.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var numberOfCellInRow : Int = 3
var padding : Int = 5
var collectionCellWidth : CGFloat = (self.view.frame.size.width/CGFloat(numberOfCellInRow)) - CGFloat(padding)
return CGSize(width: collectionCellWidth , height: collectionCellWidth)
}
You can get the size of cell via :
((UICollectionViewFlowLayout) self.collectionViewName).itemSize.height)
You can get the image size via :
let sizeOfImage = image.size
let height = image.height
If you want to change the height then change it manually by return CGSize(width: collectionCellWidth , height: cellheight)
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
}