101

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;
}
Hexfire
  • 5,324
  • 8
  • 28
  • 40
pulp
  • 1,498
  • 2
  • 13
  • 21
  • similar sort of problem ... http://stackoverflow.com/questions/24915443/uicollectionview-simply-fit-cell-to-width ??? – Fattie Jul 23 '14 at 16:03

8 Answers8

86

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];
}
Basheer_CAD
  • 4,768
  • 22
  • 35
  • 4
    You cannot imagine how I thank you! That really works. Now I only need to resolve [cell.myLabel sizeToFit] problem, because it appears in its full size only after scrolling. But I have not been even close to your solution. – pulp Apr 17 '14 at 18:43
  • thank you for your help but I still have one issue. When I uncomment [cell.myLabel sizeToFit] I have words truncated and letters cut at the bottom but it becomes ok after I scroll (words have they normal size and letters jump up a bit). If I comment and disable [cell.myLabel sizeToFit] message (I decided to play around with IB and it works fine) I have words cut at the end and bottom. I made a screenshot http://goo.gl/HaoqQV It's not very sharp on non-retina displays but you can see that letters have cut. Your suggestion on how to solve will be really appreciated! – pulp Apr 18 '14 at 07:17
  • 2
    instead of sizeToFit, use sizeWithAttributes to get the CGSize of the text, then set the label's frame with new size. – Basheer_CAD Apr 18 '14 at 09:13
  • thank you for suggestion but i still have myLabel cut at the bottom and the end. Maybe I'm wrong with implementation of you suggestion. Here's my code – pulp Apr 18 '14 at 10:30
  • `- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"qw" forIndexPath:indexPath]; cell.myLbale.text = [NSString stringWithFormat:@"%@",[arrayOfStats objectAtIndex:indexPath.item]]; CGSize textSize; textSize = [[arrayOfStats objectAtIndex:indexPath.item] sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12.0f]}]; [cell.myLbale sizeThatFits:textSize]; //[cell.myLbale sizeToFit]; return cell; }` – pulp Apr 18 '14 at 10:32
  • try this. newLabelFrame.size = // the size from sizeWithAttributes cell.myLabel.frame = newLabelFrame; – Basheer_CAD Apr 18 '14 at 11:48
  • cant assign `cell.myLabel.frame = newLabelFrame;` got error "Expression is not assignable". Anyways thanks for your attention, sorry for wasting your time. – pulp Apr 18 '14 at 12:04
  • I don't know why. Maybe you are doing something wrong. why don't you post another question about that, so people can help you ? – Basheer_CAD Apr 18 '14 at 12:06
  • This is superb, but sometimes some cells are wider than they need to be, anyway to fix that? – Supertecnoboff Oct 05 '15 at 11:38
  • @Supertecnoboff, of course you have the save so you cam easily use .width – Basheer_CAD Oct 05 '15 at 11:43
60

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.

Hexfire
  • 5,324
  • 8
  • 28
  • 40
  • 1
    don't forget to set `collectionView.delegate == self // or whatever-object-which-do-it` – Fitsyu Jun 14 '19 at 09:23
  • 1
    Great answer. Although the size I was getting was a little bit smaller than it needed to be, on different lengths of strings, I decided to modify the size myself a bit. Adding additional 5 points to the width did the trick: ```CGSize(width: title.size(withAttributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)]).width + 5, height: 50)``` – Starsky Aug 21 '20 at 13:57
40

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)
        }
Hassan Izhar
  • 501
  • 4
  • 3
  • 9
    Be careful using this. Creating and drawing a new UILabel for each cell calculation is very expensive. – AnthonyR Feb 07 '19 at 15:05
  • need add UICollectionViewDelegateFlowLayout – cristianego Apr 27 '19 at 01:13
  • 3
    To address the comment about this being expensive creating a dummy label, maybe you could just create one dummy label instead of several. All you really want from it is text size from label attributes. At the end of the day though, it is essentially the same as calculated the text size via `sizeWithAttributes`, so maybe that is the preferred answer. – Stephen Paul Jun 26 '19 at 18:03
  • @Hassan Thanks bro it worked for me, but i found a problem in width so added return CGSize(width: label.frame.width + 50, height: 32). Then it worked, i think this answer should be on top list. – Arshad Shaik Jul 12 '19 at 11:19
27

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];
}
Jordan Soltman
  • 3,671
  • 14
  • 26
Raza.najam
  • 759
  • 6
  • 15
18

In Swift 3

let size = (arrayOfStats[indexPath.row] as NSString).size(attributes: nil)
Johnson
  • 193
  • 3
  • 7
14

Swift 4

let size = (arrayOfStats[indexPath.row] as NSString).size(withAttributes: nil)
Barath
  • 1,258
  • 15
  • 18
0

//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;
Alok Nair
  • 3,884
  • 3
  • 21
  • 30
-1

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

collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Ben
  • 2,044
  • 3
  • 22
  • 38