Dynamic Width Cell in UICollectionView - ios

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

Related

Dynamic item height with equal spacing

What I want to achieve is a layout like this.
Inside to box there will be other content like image, text, button and so on. what I want is make the box sizing dynamic so that it's it takes the height of it's content. The first option came to my mind is use a collection view flow layout.
After some trial and error I end up with this layout.
My Problem is the inter item spacing I want them to be equal, is there any way to achieve this?
Any suggestion I greatly appreciated.
What I have tried so far.
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as! MyCollectionViewCell
cell.configure(title: data[indexPath.row].title)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let spacing: CGFloat = 10
let numberOfItemsPerRow:CGFloat = 2
let spacingBetweenCells:CGFloat = 10
let totalSpacing = (2 * spacing) + ((numberOfItemsPerRow - 1) * spacingBetweenCells)
let approximateWidthOfContent = view.frame.width - 32
let size = CGSize(width: approximateWidthOfContent, height: 1000)
let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]
let estimatedFrame = NSString(string: data[indexPath.row].title).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
if let collection = self.collectionView{
let width = (collection.bounds.width - totalSpacing)/numberOfItemsPerRow
return CGSize(width: width, height: estimatedFrame.height + 50)
}else{
return CGSize(width: 0, height: 0)
}
}
}
I know there are similar questions but I could not find any which meets my requirement.
After following a tutorial from raywenderlich I finally managed to achieve the layout I wanted, Thanks people who have helped me suggesting with important clues.
What I achieved
The link for the Instructions I have followed is here.

Custom UICollectionViewCell self size according to the label text

I created a UICollectionViewCell xib and would like the cell to adjust automatically according to the length of the label text like the picture shown below.
The picture shows what I made on a prototype cell. But no matter how I tried, the cell just kept the same size when I used a xib instead.
implement this delegate UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let market = self.markets[indexPath.row]
let label = UILabel(frame: CGRect.zero)
label.text = market
label.sizeToFit()
return CGSize(width: label.frame.width, height: 25)
}
or
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = NSAttributedString(string: markets[indexPath.row])
return CGSize(width: text.size().width, height: 25)
}
You can also just implement this in sizeForItemAt
just insert 'yourString' and 'yourFont':
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = yourString.size(withAttributes: [NSAttributedString.Key.font: yourFont]).width
return CGSize(width: width, height: 20)
}
you might need to add some space to the width because of the corner radius of the cell

Dynamic UICollectionView Cell Frame

Right now, i'm facing an issue.
I manage to wrap the cell's content view to its label, but there's some spacing which doesn't fit to the design. There's some spaces available between the cell
Here's my code
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let hastag = Hastag.fetchHastag()[indexPath.row]
let approximateWidth = view.frame.width - 20
let size = CGSize(width: approximateWidth, height: 28)
let attribute = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12)]
let estimatedFrame = NSString(string: hastag.hastag).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attribute, context: nil)
return CGSize(width: estimatedFrame.width + 20, height: 26)
}
Below is the result
And this is what i want to achieve, it will be something like this
UPDATE
Here is my full code of collection view data source and delegate. Hastag.hastag is type of string
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Hastag.fetchHastag().count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hastagCell", for: indexPath) as! HastagCollectionViewCell
cell.hastag = Hastag.fetchHastag()[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let hastag = Hastag.fetchHastag()[indexPath.row]
let approximateWidth = view.frame.width - 20
let size = CGSize(width: approximateWidth, height: 28)
let attribute = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12)]
let estimatedFrame = NSString(string: hastag.hastag).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attribute, context: nil)
return CGSize(width: estimatedFrame.width + 16, height: 26)
}
You can use AlignedCollectionViewFlowLayout, its not a library just one file. Just add this file to your project and from your UICollectionView xib just set the flow layout as AlignedCollectionViewFlowLayout. It is very easy to use. Please check the below link.
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
You can use the size(withAttributes:) function of a NSString to give you the CGSize of a string with a specific font and size.
Assuming that Hastag.hastag is a String I think the following should work:
extension YourViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let hastag = Hastag.fetchHastag()[indexPath.row]
let title = hastag.hastag as NSString
let titleSize = title.size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12)])
return CGSize(width: titleSize.width + 20, height: 26)
}
}
Note that your ViewController must implement the UICollectionViewDelegateFlowLayout protocol and you must set your ViewController as the delegate for the UICollectionView for this to work.
Hope that helps.
After several try, i finally give up on customizing cell size and instead i'm using the library TagListView

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!

Dynamic height for UICollectionViewCell

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.

Resources