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
}
}
Implement this in your UITableViewCell Cell
UICollectionViewFlowLayout
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!
}
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:
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
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With