Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spacing is not correct in CollectionView when cell having dynamic width?

I want to achieve a TokenView (Chip view) style view. For this I am using a collection view inside a UITableViewCell, CollectionView is having custom cell.Inside custom cell there is UILabel. Now the width of a cell is depended on content of UILabel.

Below is code for custom UITableViewCell Cell

import UIKit

class LanguageTableViewCell: UITableViewCell,UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout{

        var arr:[String] = ["English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate","English","Intermediate"]
    @IBOutlet weak var collectionView: UICollectionView!

    override func awakeFromNib() {
        super.awakeFromNib()
        collectionView.dataSource = self
        collectionView.delegate = self
        let layout = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = 0
        layout.minimumLineSpacing = 0
        layout.scrollDirection = .vertical
        self.collectionView.collectionViewLayout = layout
        // Initialization code
    }


    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return arr.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "LanguageCollectionViewCell", for: indexPath) as! LanguageCollectionViewCell
        cell.setValue(lang: arr[indexPath.row],indexPath:indexPath)
        return cell
    }



    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let string = arr[indexPath.row]
        let width = (string.count * 10)
        return CGSize(width:width, height: 30)
    }

}

class for UICollectionViewCell

typealias Parameters = [String:Any]
import UIKit


class LanguageCollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var languageLabel: UILabel!

    override func layoutSubviews() {
        super.layoutSubviews()

    }

    func setValue(lang:String ,indexPath:IndexPath) {

        if indexPath.row % 2 == 0 {
            languageLabel.backgroundColor = UIColor.gray

        }else {
            languageLabel.backgroundColor = UIColor.yellow

        }
            self.languageLabel.text = lang
    }
}

enter image description here

like image 807
TechChain Avatar asked May 25 '18 12:05

TechChain


4 Answers

Implement this in your UITableViewCell Cell

  1. Subclassing UICollectionViewFlowLayout
  2. Add implementation like that:

The Solution is reduce cell spacing/Align UICollectionView cell Layout. Very simple and quick in performance. Based on the example given by Olloqui

Class CustomViewFlowLayout

class CustomViewFlowLayout: UICollectionViewFlowLayout {

let cellSpacing:CGFloat = 4

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            self.minimumLineSpacing = 4.0
            self.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 6)
            let attributes = super.layoutAttributesForElements(in: rect)

            var leftMargin = sectionInset.left
            var maxY: CGFloat = -1.0
            attributes?.forEach { layoutAttribute in
                if layoutAttribute.frame.origin.y >= maxY {
                    leftMargin = sectionInset.left
                }
                layoutAttribute.frame.origin.x = leftMargin
                leftMargin += layoutAttribute.frame.width + cellSpacing
                maxY = max(layoutAttribute.frame.maxY , maxY)
            }
            return attributes
    }
}

where cellSpacing could be set to any value you prefer. This trick guarantees that the space between cells would be EXACTLY equal to cellSpacing.

Implementation for UITableViewCell Cell/CollectionViewController.

import UIKit

class ViewController: UIViewController , UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{

@IBOutlet weak var collectionView: UICollectionView!

var arr:[String] = ["English","Intermediate","English","English","arr","UICollectionViewFlowLayoutFlowFlowFlow","English","UICollectionViewDelegate","English","Intermediate","UIViewController","viewDidLoad","Intermediate","String","Intermediate","arr","Intermediate","UIKit","Intermediate","English","columnLayout","English","languageLabel"]

let columnLayout = CustomViewFlowLayout()

override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.collectionViewLayout = columnLayout
    collectionView.contentInsetAdjustmentBehavior = .always
}


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return arr.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "chip", for: indexPath) as! LanguageCollectionViewCell
    cell.languageLabel.text = arr[indexPath.row]
    return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    let string = arr[indexPath.row]

    // your label font.
    let font = UIFont.systemFont(ofSize: 16)
    let fontAttribute = [NSAttributedStringKey.font: font]

    // to get the exact width for label according to ur label font and Text.
    let size = string.size(withAttributes: fontAttribute)

    // some extraSpace give if like so.
    let extraSpace : CGFloat = 8.0
    let width = size.width + extraSpace
    return CGSize(width:width, height: 30)
   }
}

class for UICollectionViewCell

class LanguageCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var languageLabel: UILabel!

}

enter image description here

like image 193
Govind Kumawat Avatar answered Oct 17 '22 06:10

Govind Kumawat


I have used a pod for the same

pod 'AlignedCollectionViewFlowLayout'

And in the class where I use my UICollectionView,

import AlignedCollectionViewFlowLayout

and configured the collectionViewLayout as follows

if let fl = myCollection.collectionViewLayout as? AlignedCollectionViewFlowLayout {          
  fl.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
  fl.horizontalAlignment = .left
  fl.verticalAlignment = .top
}

Note that you can set left/right and top/bottom alignments with this pod! It works like a charm for me

I also made sure to add preferredMaxLayoutWidth to my label in the UICollectionViewCell so that I can fix one of the possible errors (i.e width of the cell can not be greater than screen size)

class MyCollectionViewCell: UICollectionViewCell {
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        myLabel.preferredMaxLayoutWidth = UIScreen.main.bounds.size.width * 0.8
    }
}

And I get the result as follows for my collection:

enter image description here

like image 5
Mansi Avatar answered Oct 17 '22 04:10

Mansi


1-implement UICollectionViewDelegateFlowLayout protocol,

add the following code :

var itemsPerRow:CGFloat = 3

let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

then add the delegate methods to determine spacing like below;

func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
        let availableWidth = view.frame.width - paddingSpace

        let widthPerItem = availableWidth / itemsPerRow

        return CGSize(width: widthPerItem, height: widthPerItem)
    }

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        insetForSectionAt section: Int) -> UIEdgeInsets {
        return sectionInsets
    }

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 10
    }
like image 4
Sanad Barjawi Avatar answered Oct 17 '22 04:10

Sanad Barjawi


Can you try

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    let lb = UILabel()

    lb.text = arr[indexPath.row]

    lb.font = UIFont.init(name: "System", size: 17)

    lb.sizeToFit()

    return CGSize(width:lb.frame.size.width, height: 30 )
}

//

or set this

layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

and give the label 0-based leading,trailing,top and bottom constraints to the cell

Note: in both cases you have to remove implementation of method sizeForItemAt

like image 2
Sh_Khan Avatar answered Oct 17 '22 06:10

Sh_Khan