CollectionView dynamic height with Swift 3 in iOS
Provide
estimatedSize
to yourUICollectionViewLayout
.The estimated size should be the size shown in the size inspector of your xib.
collectionViewLayout.estimatedItemSize = CGSize(width: collectionView.frame.width, height: 50)
Override the method
preferredLayoutAttributesFitting(_:)
in yourUICollectionViewCell
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
Your collection view cell will now have dynamic size as per the content.
all what you need to do is making an IBOutlet for the UICollectionView layout,
set the estimatedItemSize
to any size,
in your cell class you have to specify the cell width (if you just want the height to be dynamic and the width is static) and/or height in awakeFromNib
and disable translatesAutoresizingMaskIntoConstraintsself.contentView.translatesAutoresizingMaskIntoConstraints = false
.
so the final result should be something like this
class ProfileViewController: UIViewController {
@IBOutlet var collectionView: UICollectionView!
@IBOutlet var collectionLayout: UICollectionViewFlowLayout!
var person: Person!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: "ProfileCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
collectionLayout.estimatedItemSize = CGSize(width: screenWidth * 0.4, height: 1)
collectionView.reloadData()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ProfileCollectionViewCell
return cell
}
}
and your cell class should be something like this
class ProfileCollectionViewCell: UICollectionViewCell {
@IBOutlet var containerWidthConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.translatesAutoresizingMaskIntoConstraints = false
containerWidthConstraint.constant = screenWidth - (2 * 12)
}
}
and this is a good tutorial for self sizing in ios https://engineering.shopspring.com/dynamic-cell-sizing-in-uicollectionview-fd95f614ef80
Use the following code to change the height according to the text displayed:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(view.frame.width , 64)
}
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let approximateWidthOfContent = view.frame.width - x
// x is the width of the logo in the left
let size = CGSize(width: approximateWidthOfContent, height: 1000)
//1000 is the large arbitrary values which should be taken in case of very high amount of content
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 15)]
let estimatedFrame = NSString(string: user.bioText).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 66)
}
I solved it :)
I just tried to compute my cell's width and expect height in sizeForItemAt function and it works !
Code below :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let imageShowingWidth: CGFloat = self.view.frame.width / CGFloat(self.howManyImageShowing)
let labelName = "@\(self.tweetShowing[indexPath.row].user.screenName) (\(self.tweetShowing[indexPath.row].user.name))"
let labelNameFont: UIFont = UIFont(name: "PingFangTC-Semibold", size: 16)!
let labelNameWidth: CGFloat = self.view.frame.width - YourWidthOffSet// (YourWidthOffSet include all images' width and all margins)
let labelNameHeight: CGFloat = self.getHeightForLable(labelWidth: labelNameWidth, labelText: labelName, labelFont: labelNameFont)
let labelContentFont: UIFont = UIFont(name: "PingFangTC-Regular", size: 16)!
let labelContentHeight: CGFloat = self.getHeightForLable(labelWidth: labelNameWidth, numberOfLines: 0, labelText: self.tweetShowing[indexPath.row].text, labelFont: labelContentFont)
let cellHeight: CGFloat = labelNameHeight + labelContentHeight + YourHeightOffSet // (YourHeightOffSet means all margins)
return self.typeControl.selectedSegmentIndex == 0 ? CGSize(width: self.view.frame.width, height: cellHeight) : CGSize(width: imageShowingWidth, height: imageShowingWidth)
}
func getHeightForLable(labelWidth: CGFloat, numberOfLines: Int = 1, labelText: String, labelFont: UIFont) -> CGFloat {
let tempLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: labelWidth, height: CGFloat.greatestFiniteMagnitude))
tempLabel.numberOfLines = numberOfLines
tempLabel.text = labelText
tempLabel.font = labelFont
tempLabel.sizeToFit()
return tempLabel.frame.height
}