I have created an UICollectionView horizontal scroll through programmatically. UICollectionView cell width is dynamically created based on the content.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath) as! CustomCollectionViewCell
cell.label.text = dataSource[indexPath.item]
cell.label.font = UIFont(name: "Helvetica", size: 20.0)
if currentPageIndex == indexPath.item {
cell.currentPageIndicator.frame = CGRect(x: 0, y: cell.bounds.height - 2, width: cell.bounds.width, height: 2.5)
cell.currentPageIndicator.isHidden = false
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let size = cellSizeCache.object(forKey: indexPath as AnyObject) as? NSValue {
return size.cgSizeValue
}
let string = dataSource[indexPath.item]
let height = collectionView.frame.size.height
let font = UIFont(name: "Helvetica", size: 20.0)
var size = string.boundingRect(with: CGSize(width: CGFloat.infinity, height: height), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font!], context: nil).size
size.width += 20
size.height += 20
return size
}
Initially, an UICollectionView is looking fine, even when I scroll horizontally.
When I start scrolling fast, cell's are overlap with each other.
Your cells are overlapping because the width of the cell is lesser then the size of content. You have two options, first option is to increase the width of cell in storyboard. Second option is to set the size for each cell in collectionview method named "sizeforcellatindexpath", sample method is given below:
optional func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return size
}
you can calculate the size of string by using calculation mechanism. Sample for collection in given below:
How to calculate the width of a text string of a specific font and font-size?
Related
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)
}
I try to implement labels with UICollectionView. But I have to set the widths with dynamic labels length. I try to remove gaps in picture. I share some code. I use Nibs.
Thanks in advance.
layoutFCV.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
let filterCV = UICollectionView(frame: self.view.bounds, collectionViewLayout: layoutFCV)
--
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == filterCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterSelectionCollectionViewCell", for: indexPath) as! FilterSelectionCollectionViewCell
return CGSize(width: cell.title.frame.width , height: self.filterCollectionView.frame.height)
} else {
return CGSize(width: 10, height: 10)
}
}
Use this code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = "Title"
let width = self.estimatedFrame(text: text, font: UIFont.systemFont(ofSize: 14)).width
return CGSize(width: width, height: 50.0)
}
func estimatedFrame(text: String, font: UIFont) -> CGRect {
let size = CGSize(width: 200, height: 1000) // temporary size
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size,
options: options,
attributes: [NSFontAttributeName: font],
context: nil)
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return "String".size(withAttributes: [
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 14)
])
}
}
First make sure your constraints in UICollectionViewCell.xib file has been set correctly. I mean with the growth of UILabel in cell, the cell itself should grow as well.
You should explicitly set the title before you get the cell size. So here is your collectionView(collectionView:, layout:, sizeForItemAt:) method should be:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == filterCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterSelectionCollectionViewCell", for: indexPath) as! FilterSelectionCollectionViewCell
cell.title.text = "Newest" //this is for the "Newest" cell. Of curse you should set the proper title for each indexPath
cell.setNeedsLayout()
cell.layoutIfNeede()
return CGSize(width: cell.contenView.frame.width , height: cell.contentView.frame.height)
} else {
return CGSize(width: 10, height: 10)
}
}
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!
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
I want dynamic cell Size as per text in TextView,
Here is my code
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return DataArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CustomCollectionViewCell
let fixedWidth = cell?.TextView.frame.size.width
cell?.TextView.sizeThatFits(CGSize(width: fixedWidth!, height: CGFloat.greatestFiniteMagnitude))
let newSize = cell?.TextView.sizeThatFits(CGSize(width: fixedWidth!, height: CGFloat.greatestFiniteMagnitude))
var newFrame = cell?.TextView.frame
newFrame?.size = CGSize(width: max((newSize?.width)!, fixedWidth!), height: (newSize?.height)!)
cell?.TextView.frame = newFrame!
TextViewFrame = newFrame!
cell?.TextView.text = DataArray[indexPath.row] as? String
return cell!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: CV.frame.size.width, height: 200)
}
Here i have given 200 fixed size for cell. it gives me output as below
It gives me cell height 200 for small content as well as for large content .
But actually i want cell height as per content.
How can i achieve this?
Any help is appreciated. Thanks in advance
You can find the height of the cell once you know the bounding rect for the text you want to display.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let text = "String that you want to show in the label inside the cell"
let boundingRect = NSString(string: text).boundingRectWithSize(CGSizeMake(collectionView.bounds.width, 1000),
options: .UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: UIFont.systemFontOfSize(11.0)],
context: nil)
let size = CGSizeMake(collectionView.bounds.width, ceil(boundingRect.height))
return size
}
Just be sure to make up for the top/bottom/left/right margins of your label to the cell.