pulp
pulp

Reputation: 1828

Dynamic cell width of UICollectionView depending on label width

I have a UICollectionView, that loads cells from reusable cell, which contains label. An array provides content for that label. I can resize label width depending on content width easily with sizeToFit. But I cannot make cell to fit label.

Here's the code

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    arrayOfStats =  @[@"time:",@"2",@"items:",@"10",@"difficulty:",@"hard",@"category:",@"main"];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:     (NSInteger)section{
    return [arrayOfStats count];
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{

    return CGSizeMake(??????????);
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{

    return 1;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{

    Cell *cell = (Cell *) [collectionView dequeueReusableCellWithReuseIdentifier:@"qw" forIndexPath:indexPath];
    cell.myLabel.text = [NSString stringWithFormat:@"%@",[arrayOfStats objectAtIndex:indexPath.item]];
    // make label width depend on text width
    [cell.myLabel sizeToFit];

    //get the width and height of the label (CGSize contains two parameters: width and height)
    CGSize labelSize = cell.myLbale.frame.size;

    NSLog(@"\n width  = %f height = %f", labelSize.width,labelSize.height);

    return cell;
}

Upvotes: 124

Views: 131850

Answers (11)

Mani
Mani

Reputation: 3477

implement

UICollectionViewDelegateFlowLayout

and add a code

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

    let label = UILabel(frame: CGRect.zero)
    label.text = arr[indexPath.row]
    label.sizeToFit()
    return CGSize(width: (label.frame.width+20), height: label.frame.size.height+20)

}

Upvotes: 2

Chithian
Chithian

Reputation: 349

//Create UICollectionView

lazy var collectionView: UICollectionView = {
    
    let layout = UICollectionViewFlowLayout()
    layout.scrollDirection = .horizontal
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    
    
    //CollectionCellView width autoSize
    layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
   
    collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
   [enter image description here][1]
    return collectionView
}()

Upvotes: 0

Naresh
Naresh

Reputation: 17932

In Swift 5.X, this single line of code is enough.

In the below answer i have added dynamic width and static height. Based on your requirement change height value.

//MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: itemsArray[indexPath.item].size(withAttributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 17)]).width + 25, height: 30)
}

Add UICollectionViewDelegateFlowLayout to your delegate

It's perfectly worked to me with equal cell space with dynamic cell size based on text length.

PFA image below...

enter image description here

Fix height of the collectionView (not cell )based on content(Dynamic height for collection view)

How to adjust height of UICollectionView to be the height of the content size of the UICollectionView?

Upvotes: 27

Ben
Ben

Reputation: 3814

This one line for my UICollectionViewFlowLayout() did the trick for me:

collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Upvotes: 1

Hexfire
Hexfire

Reputation: 6058

Swift 4.2+

Principle is:

  1. Make sure delegation is set up (e.g. collectionView.delegate = self)

  2. Implement UICollectionViewDelegateFlowLayout (it contains necessary method signature).

  3. Call collectionView...sizeForItemAt method.

  4. No need to bridge-cast String to NSString to call size(withAttributes: method. Swift String has it out of the box.

  5. Attributes are the same you set for (NS)AttributedString, i.e. font family, size, weight, etc. Optional parameter.


Sample solution:

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return "String".size(withAttributes: nil)
    }
}

But you would most likely want to specify concrete string attributes respective to your cell, hence final return would look like:

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // dataArary is the managing array for your UICollectionView.
        let item = dataArray[indexPath.row]
        let itemSize = item.size(withAttributes: [
            NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 14)
        ])
        return itemSize
    }
}

Why you SHOULD NOT use UILabel to calculate the size? Here's the suggested solution:

let label = UILabel(frame: CGRect.zero)
label.text = textArray[indexPath.item]
label.sizeToFit()

Yes, you get same result. It looks simplistic and may seem as a go-to solution. But it's improper because: 1) it's expensive, 2) overhead and 3) dirty.

It's expensive because UILabel is a complex UI object, which is being created on every iteration whenever your cell is about to show even though you don't need it here. It's an overhead solution because you only need to get size of a text, but you go as far as to create a whole UI object. And it's dirty for that reason.

Upvotes: 82

Hassan Izhar
Hassan Izhar

Reputation: 571

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

For dynamic height & fixed width:

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: 120, height: label.frame.height)
        }

Upvotes: 47

Raza.najam
Raza.najam

Reputation: 769

Checkout below code you might be giving very short CGSize.

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{

    NSString *testString = @"SOME TEXT";
    return [testString sizeWithAttributes:NULL];
}

Upvotes: 27

Barath
Barath

Reputation: 1764

Swift 4

let size = (arrayOfStats[indexPath.row] as NSString).size(withAttributes: nil)

Upvotes: 15

Apurva Gaikwad
Apurva Gaikwad

Reputation: 11

//add in view didload

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    [layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
    layout.estimatedItemSize = CGSizeMake(self.breadScrumbCollectionView.frame.size.width, 30); 
self.breadScrumbCollectionView.collectionViewLayout = layout;

Upvotes: 0

Johnson
Johnson

Reputation: 243

In Swift 3

let size = (arrayOfStats[indexPath.row] as NSString).size(attributes: nil)

Upvotes: 20

Basheer_CAD
Basheer_CAD

Reputation: 4919

In sizeForItemAtIndexPath return the size of the text

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{

    return [(NSString*)[arrayOfStats objectAtIndex:indexPath.row] sizeWithAttributes:NULL];
}

Upvotes: 90

Related Questions