121

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,945
  • 8
  • 32
  • 42
pulp
  • 1,768
  • 2
  • 16
  • 24
  • similar sort of problem ... http://stackoverflow.com/questions/24915443/uicollectionview-simply-fit-cell-to-width ??? – Fattie Jul 23 '14 at 16:03

11 Answers11

90

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,908
  • 24
  • 36
  • 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
79

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,945
  • 8
  • 32
  • 42
  • 1
    don't forget to set `collectionView.delegate == self // or whatever-object-which-do-it` – Fitsyu Jun 14 '19 at 09:23
  • 2
    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
  • This was very good solution, thx so much. lots of love. I was implementing a custom segment bar and for that I used this code snippet. – Bevan Jul 03 '21 at 16:02
45

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
  • 551
  • 4
  • 3
  • 12
    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,795
  • 17
  • 31
Raza.najam
  • 769
  • 6
  • 15
22

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?

Naresh
  • 16,698
  • 6
  • 112
  • 113
  • The gapping between the cell varies. Do you have any solution to have a equal spacing between the cells? – iPeter Sep 02 '21 at 18:41
  • I have added my collection view, please find it. I haven't observed any unequal cell spacing. First check your entire code, is there anything wrong If you want any help i will share the code send me your contact like mail or etc.... – Naresh Sep 03 '21 at 06:15
  • tirthendudass@gmail.com Please send your contact here. – iPeter Sep 03 '21 at 13:14
  • @ iPeter, I shared already my code with you. Have you got? Is this helpful to you? – Naresh Sep 14 '21 at 05:53
  • you saved my ass... but make sure to implement `UICollectionViewDelegateFlowLayout` otherwise its never going to show any error neither changes are going to reflect ... – Wahab Khan Jadon Oct 04 '21 at 14:48
  • Fix height of the collectionView (not cell )based on content...https://stackoverflow.com/questions/42437966/how-to-adjust-height-of-uicollectionview-to-be-the-height-of-the-content-size-of/56802898#56802898 – Naresh Oct 18 '21 at 05:47
20

In Swift 3

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

Swift 4

let size = (arrayOfStats[indexPath.row] as NSString).size(withAttributes: nil)
Barath
  • 1,656
  • 19
  • 19
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,994
  • 3
  • 24
  • 30
0

//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
}()
Chithian
  • 253
  • 3
  • 8
0

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)

}
Mani
  • 3,394
  • 3
  • 30
  • 36
-1

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

collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Ben
  • 3,346
  • 6
  • 32
  • 51