HorizontalCollectionView Content width and spacing - ios

How can I make my horizontal collection view labels width to wrap the content width of the label and make them have equal spacing between each of them? Currently I have the collection view cell width as 100. If I increase the width to fit other labels, the shorter labels have more spacing between them. Any ideas will be appreciated.

Calculate the width of the label text first with the font associated with the text.
extension String {
func size(with font: UIFont) -> CGSize {
let fontAttribute = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttribute)
return size
}
}
Return the calculated width along with collectionView height in collectionView(_, collectionViewLayout:_, sizeForItemAt).
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let newWidth = titles[indexPath.row].size(with: labelFont!).width + 10 //Added 10 to make the label visibility very clear
return CGSize(width: newWidth, height: collectionView.bounds.height)
}
Entire source code:
class ViewController: UIViewController {
#IBOutlet weak var collection: UICollectionView!
let labelFont = UIFont(name: "Helvetica Neue", size: 18)
let titles = ["Hi", "Hello", "HorizontalCollectionView", "VerticalCollectionView"]
override func viewDidLoad() {
super.viewDidLoad()
collection.backgroundColor = UIColor(red: 68/255, green: 143/255, blue: 1, alpha: 1)
collection.register(UINib.init(nibName: "CustomCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CustomCollectionViewCell")
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return titles.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10 // Adjust the inter item space based on the requirement.
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let newWidth = titles[indexPath.row].size(with: labelFont!).width + 10 //Added 10 to make the label visibility very clear
return CGSize(width: newWidth, height: collectionView.bounds.height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath) as! CustomCollectionViewCell
cell.titleLabel.text = titles[indexPath.row]
cell.titleLabel.font = labelFont!
return cell
}
}
extension String {
func size(with font: UIFont) -> CGSize {
let fontAttribute = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttribute)
return size
}
}
Another Solution:
ViewController.swift
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var collView: UICollectionView!
var tasksArray = ["To Do", "SHOPPING","WORK"]
var selectedIndex = Int()
override func viewDidLoad() {
super.viewDidLoad()
collView.register(UINib.init(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
let layout = collView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = UICollectionViewFlowLayout.automaticSize
layout.estimatedItemSize = CGSize(width: 170, height: 50)
// 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
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath.row
self.collView.reloadData()
}
}
Cell.swift
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var lblName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
layer.borderWidth = 1
layer.cornerRadius = bounds.height / 2
}
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
}
}

You have to manually calculate the width of each cell. To calculate the width, you actually calculate the width of the text residing in that cell.
use following
boundingRect(with:options:attributes:context:)
https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrect
to calculate the width of a given string. So after calculating the returning the size the cell should be wrapping the text.
Now come the second part, equal spacing between items. use interItemSpacing to define the spacing between each cell.

Related

How to show three rounded images in Collectionview cell in all iPhone screen sizes in swift

I have taken collectionview in storyboard and in its cell i have taken ImageView and its below one label
i need to show only three rounded images horzontally and number of rows from JSON vertically in all screen sizes
like below image i need:
in cell for imageview costraints like below
leading = trailing = top = bottom = 5
and in coding:
class FriendsViewController: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var galleryCollectionView: UICollectionView!
// MARK: - LifeCycleMethods
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
layout.scrollDirection = .vertical
let width = UIScreen.main.bounds.width/4
let height = width*1.1
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 5
layout.itemSize = CGSize(width: width, height: height)
self.galleryCollectionView.collectionViewLayout = layout
}
}
class FriendsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imgView: UIImageViewX!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
imgView.layer.cornerRadius = imgView.frame.height/2.0 }
}
so i am getting weird o/p in different screen sizes why?
o/p of ipodtouch7
In your collectionview cell Set constraints as shown in attached image, i.e. ur image view width should depend on imageview height, you can do it using ratio.
Then in willDisplay method calculate cornerRadius
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
{
guard let cell = cell as? TestCollectionViewCell else
{
return
}
let cellHeight : CGFloat = cell.frame.size.height
let labelHeight : CGFloat = cell.label?.frame.size.height ?? 0.0
cell.imageView?.layer.cornerRadius = (cellHeight - labelHeight) / 2.0
}
Below is the entire code
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
#IBOutlet weak var collectionView : UICollectionView? = nil
let horizontalSpaceBetweenCell : CGFloat = 16
let verticalSpaceBetweenCell : CGFloat = 16
let edgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
override func viewDidLoad()
{
super.viewDidLoad()
setup()
}
func setup()
{
let nib = UINib(nibName: "TestCollectionViewCell", bundle: nil)
collectionView?.register(nib, forCellWithReuseIdentifier: "TestCollectionViewCell")
}
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 40
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TestCollectionViewCell", for: indexPath) as? TestCollectionViewCell
{
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
{
guard let cell = cell as? TestCollectionViewCell else
{
return
}
let cellHeight : CGFloat = cell.frame.size.height
let labelHeight : CGFloat = cell.label?.frame.size.height ?? 0.0
cell.imageView?.layer.cornerRadius = (cellHeight - labelHeight) / 2.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let numberOfCellsPerRow = 3
let otherSpace = (edgeInsets.left + edgeInsets.right)
let width = (collectionView.frame.size.width - otherSpace) / CGFloat(numberOfCellsPerRow)
return CGSize(width: width, height: 80) // Height can be anything 80, 90 ,100
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 16
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return edgeInsets
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
}
}
Please change Widhth to height :
imgView.layer.cornerRadius = imgView.frame.size.height/2.0

All CollectionViewCell height equal to largest cell label dynamic content

I need to create a vertical CollectionView which have a dynamic label and all CollectionViewCell height should be same to the largest cell content. How to calculate largest cell height and assign that height to every cell?
I have tried many solution but none is working for me.
Any solution pls
Thanks in advance.
Start by adding the following UILabel Extension (add anywhere outside of the ViewController):
extension UILabel{
public var getHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
Then you can calculate the height for each label while the collection view cell is rendering. In the following, I check to see if the new height is greater then the last tallest height - this is saved to my variable called tallestCellHeight. Use this variable to set the height of the cell.
var tallestCellHeight: CGFloat = 0
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "testCell", for: indexPath) as! testCell
cell.testLabel.text = messages[indexPath.row]
let currentCellHeight = cell.testLabel.getHeight
if currentCellHeight > tallestCellHeight {
tallestCellHeight = currentCellHeight
print(tallestCellHeight)
}
cell.backgroundColor = .tertiarySystemFill
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: 150, height: tallestCellHeight)
}
After the collection view has rendered, I will have the tallest cell height saved to the variable tallestCellHeight.
Lastly, reload the collection view on the main thread in viewDidLoad()
DispatchQueue.main.async {
self.collectionView.reloadData()
}
Don't forget to include UICollectionViewDelegate, UICollectionViewDataSource and UICollectionViewDelegateFlowLayout
Entire Code
import UIKit
let messages: [String] = ["hello and welcome", "this is a really cool app", "this is awesome", "welcome to the test for my new application", "hello everyone"]
class testCell: UICollectionViewCell {
#IBOutlet weak var testLabel: UILabel!
}
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
var tallestCellHeight: CGFloat = 0
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "testCell", for: indexPath) as! testCell
cell.testLabel.text = messages[indexPath.row]
let currentCellHeight = cell.testLabel.getHeight
if currentCellHeight > tallestCellHeight {
tallestCellHeight = currentCellHeight
print(tallestCellHeight)
}
cell.backgroundColor = .tertiarySystemFill
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: 150, height: tallestCellHeight)
}
}
extension UILabel{
public var getHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}

How to match the horizontal collectionView Cell's width on it's content size

I know some related questions already there regarding this but I tried those before and still no luck.
Here is my problem on the following screenshot
My current screen shows a fixed cell size and it displays empty spaces for smaller contents and larger contents going over the cell with dots..
I wanted like below
it should match the width of the product category name content.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.catCollectionView{
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
DispatchQueue.main.async {
catCell.configureCell()
let catCountInt = self.catCountArray[indexPath.row]
catCell.catCountLabel.text = String(catCountInt)
catCell.catNameLabel.text = self.categoryNameArray[indexPath.row]
catCell.catCountLabel.sizeToFit()
catCell.catNameLabel.sizeToFit()
}
return catCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let catCell = collectionView.dequeueReusableCell(withReuseIdentifier: "catCell", for: indexPath)
as! catCell
catCell.catNameLabel.text = self.categoryNameArray[indexPath.item]
catCell.catNameLabel.sizeToFit()
let labelWidth = catCell.catNameLabel.frame.width + 10
print("frame width: \(labelWidth)")
return CGSize(width: labelWidth, height: 21)
}
}
Maybe I'm missing a simple thing here but I couldn't quite figure out at this moment. Please help me and sorry for my strange English.
Let's imagine you are using a simple UICollectionViewCell subclass with constraints set up correctly in storyboard (label is pinned to all four sides of its superview) like this:
class CategoryCell: UICollectionViewCell {
#IBOutlet var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
layer.borderWidth = 1
}
}
Then you can simply let auto layout determine the cells' sizes:
class CollectionViewController: UICollectionViewController {
let categories = [
"All Products",
"Fresh",
"Health & Beauty",
"Beverages",
"Home & life"
]
private var flowLayout: UICollectionViewFlowLayout? {
return collectionViewLayout as? UICollectionViewFlowLayout
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .white
flowLayout?.sectionInset = .init(top: 15, left: 15, bottom: 15, right: 15)
flowLayout?.sectionInsetReference = .fromSafeArea
flowLayout?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
DispatchQueue.main.async {
self.flowLayout?.invalidateLayout()
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoryCell", for: indexPath) as! CategoryCell
cell.nameLabel.text = categories[indexPath.row]
return cell
}
}
Result:
Instead of dequeuing another cell in collectionView(_:layout:sizeForItemAt:), you can simply calculate the width of categoryName using size(withAttributes:) on the categoryName, i.e.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = self.categoryNameArray[indexPath.row] {
let cellWidth = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:14.0)]).width + 10.0
return CGSize(width: cellWidth, height: 21.0)
}
in attributes, give whatever font you want the categoryName to be.

How to add Footer / Header in CollectionView

code Snippet
class TemporaryViewController: UIViewController {
#IBOutlet weak var itemCollectionView: UICollectionView! // Main Collection View
private var footerView: DashboardBottomViewCollectionViewCell?
override func viewDidLoad() {
super.viewDidLoad()
self.itemCollectionView.delegate = self
self.itemCollectionView.dataSource = self
self.itemCollectionView.register(UINib(nibName: nibParameter.DASHBOARD_CENTER_NIB, bundle: nil), forCellWithReuseIdentifier: nibParameter.DASHBOARD_CENTER_NIB)
self.itemCollectionView.register(UINib(nibName: "DashboardBottomViewCollectionViewCell", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "DashboardBottomViewCollectionViewCell")
}}
extension TemporaryViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
return nil
case UICollectionView.elementKindSectionFooter:
let footerView = itemCollectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "DashboardBottomViewCollectionViewCell", for: indexPath) as! DashboardBottomViewCollectionViewCell
self.footerView = footerView
self.footerView?.contentView?.clipsToBounds = true
footerView?.dashboardBottomDelegate = self
footerView?.clipsToBounds = true
return footerView!
default:
return headerView!
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: 600, height: 300.0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: 100.0, height: 300.0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = -10 + (collectionView.frame.width)
return CGSize(width: width,
height: 90)
}
}
Footer Class to Show collection View
class DashboardBottomViewCollectionViewCell: UICollectionReusableView {
weak var dashboardBottomDelegate: DashboardBottomViewCollectionViewDelegate?
#IBOutlet weak var itemCollectionView: UICollectionView!
#IBOutlet weak var imageView: UIView!
var estimateWidth = 160.0
var cellMarginSize = 16.0
var contentView : DashboardBottomViewCollectionViewCell?
override init(frame: CGRect) {
super.init(frame: frame)
let contents = Bundle.main.loadNibNamed("DashboardBottomViewCollectionViewCell", owner: self, options: nil)?.first as! DashboardBottomViewCollectionViewCell
self.addSubview(contents)
contentView = contents
contents.itemCollectionView.register(UINib(nibName: nibParameter.DASHBOARD_PURCHASE_NIB, bundle: nil), forCellWithReuseIdentifier:nibParameter.DASHBOARD_PURCHASE_NIB)
contents.itemCollectionView.delegate = self
contents.itemCollectionView.dataSource = self
let flow = contents.itemCollectionView?.collectionViewLayout as! UICollectionViewFlowLayout
flow.minimumInteritemSpacing = CGFloat(self.cellMarginSize)
flow.minimumLineSpacing = CGFloat(self.cellMarginSize)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func reloadData() {
contentView?.itemCollectionView.reloadData()
}
}
extension DashboardBottomViewCollectionViewCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 15
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: nibParameter.DASHBOARD_PURCHASE_NIB, for: indexPath) as! RecentPurchaseCollectionViewCell
cell.purchaseLabel.text = "trainers.trainerName"
cell.imageView.sd_setImage(with: URL(string: "trainers.tarinerProfilePictureUrl" != nil ? "trainers.tarinerProfilePictureUrl" : ""), placeholderImage: UIImage.init(named: "tour_placeholder"), options: .handleCookies, completed: nil)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = 0 + (collectionView.frame.width / 1)
return CGSize(width: width,
height: 100)
}
}
extension DashboardBottomViewCollectionViewCell : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
dashboardBottomDelegate?.didSelect(tem: indexPath.item)
}
}
As per the code, I can see this:
I can see a collectionView in Footer but can't change the orientation to vertical instead of the Grid view like this:
I am stuck and couldn't find any hint. Any hint would be appreciable. Thanks
Have you tried putting your setup code DashboardBottomViewCollectionViewCell.init(frame:) in awakeFromNib() instead?
override func awakeFromNib() {
super.awakeFromNib()
//setup cell and stuffs
}
Also call footerView?.reloadData() to reload the contents.

issue while set dynamic width of collection view cell

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

Resources