20

I have a UITableview with multiple reusable TableViewCells. In one cell I have a UITextView, that resizes itself to fit its content. Now I "just" have to resize the contentView of the TableViewCell, so I can read the while text. I already tried:

cell2.contentView.bounds.size.height = cell2.discriptionTextView.bounds.size.height; 

Or:

cell2.contentView.frame = CGRectMake(0, cell2.discriptionTextView.bounds.origin.y,     
cell2.discriptionTextView.bounds.size.width,     
cell2.discriptionTextView.bounds.size.height); 

In the method:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath 
*)indexPath {}  

But it won't work.

Does anyone know how to do this?

New code:

    @implementation AppDetail

    CGFloat height;
    …

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {…

    cell2.TextView.text = self.text;
            [cell2.TextView sizeToFit];
            height = CGRectGetHeight(cell2.TextView.bounds);
     …
    }

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

        if (indexPath.row == 0) {
            return 143;
        }
        if (indexPath.row == 1) {
            return height;
        }

        return 0;
    }
David Gölzhäuser
  • 3,525
  • 8
  • 50
  • 98

8 Answers8

18

You can only resize a UITableViewCell in tableView:heightForRowAtIndexPath: delegate method.

You have to estimate what the size of the text will be when that method is called for every row when the tableView is loaded.

This is what I did to solve the problem.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
 {
     NSString * yourText = self.myArrayWithTextInIt[indexPath.row]; // or however you are getting the text
     return additionalSpaceNeeded + [self heightForText:yourText];
 }

 -(CGFloat)heightForText:(NSString *)text
 {
   NSInteger MAX_HEIGHT = 2000;
   UITextView * textView = [[UITextView alloc] initWithFrame: CGRectMake(0, 0, WIDTH_OF_TEXTVIEW, MAX_HEIGHT)];
   textView.text = text;
   textView.font = // your font
   [textView sizeToFit];
   return textView.frame.size.height;
  }

EDIT

While I used this solution for a while, I found a more optimal one that I would recommend using as it doesn't require allocating an entire textView in order to work, and can handle text greater than 2000.

-(CGFloat)heightForTextViewRectWithWidth:(CGFloat)width andText:(NSString *)text
{
    UIFont * font = [UIFont systemFontOfSize:12.0f];

    // this returns us the size of the text for a rect but assumes 0, 0 origin
    CGSize size = [text sizeWithAttributes:@{NSFontAttributeName: font}];

    // so we calculate the area
    CGFloat area = size.height * size.width;

    CGFloat buffer = whateverExtraBufferYouNeed.0f;

    // and then return the new height which is the area divided by the width
    // Basically area = h * w
    // area / width = h
    // for w we use the width of the actual text view
    return floor(area/width) + buffer;
}
AdamG
  • 3,718
  • 2
  • 18
  • 28
  • What are you trying to do? Resize the contentView of the textView or the cell? – AdamG Oct 06 '13 at 20:28
  • 1
    The method I showed above will allow you to re-size the size of the cell itself based on the size of the contentView of a given textView in a cell. All you need to do is find the text you are using for the given cell. – AdamG Oct 06 '13 at 20:29
  • your answer is incomplete what is `additionalSpaceNeeded` –  Feb 18 '14 at 19:42
  • Hey Al Pacino, that should be self explanatory, additionalSpaceNeeded is any additional space you need beyond the textView. – AdamG Feb 19 '14 at 21:01
  • How do you change the width, though? – Phillip Jul 17 '14 at 23:08
  • in the edit it isn't clear to me why you are scaling self.height by multiplying it by self.width/width. Why not just return self.height + buffer ? – duckstar Feb 23 '16 at 13:37
16

As @Rob Norback said, There is something called UITableViewAutomaticDimension.

For Swift, The easiest way to resize content from UITableViewCell on the fly is to just add this.

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
     return UITableViewAutomaticDimension
}
r_19
  • 1,858
  • 1
  • 20
  • 16
  • It appears this only works if all constraints for the cell are set relative to the cell. https://stackoverflow.com/a/30300870/957245 – Declan McKenna Sep 18 '17 at 11:40
14

Here's an updated version for iOS 7+ that is cleaner (no extra method)

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UIFont * font = [UIFont systemFontOfSize:15.0f];
        NSString *text = [getYourTextArray objectAtIndex:indexPath.row];
        CGFloat height = [text boundingRectWithSize:CGSizeMake(self.tableView.frame.size.width, maxHeight) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:@{NSFontAttributeName: font} context:nil].size.height;

        return height + additionalHeightBuffer;
    }
enc_life
  • 4,973
  • 1
  • 15
  • 27
8

You need you implement heightForRowAtIndexPath.

Say that the data that is to be displayed in the textView is stored in a NSArray.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
 CGFloat cellheight = 30; //assuming that your TextView's origin.y is 30 and TextView is the last UI element in your cell

 NSString *text = (NSString *)[textArray objectAtIndex:indexpath.row];
 UIFont *font = [UIFont systemFontOfSize:14];// The font should be the same as that of your textView
 CGSize constraintSize = CGSizeMake(maxWidth, CGFLOAT_MAX);// maxWidth = max width for the textView

 CGSize size = [text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];

 cellHeight += size.height; //you can also add a cell padding if you want some space below textView

}
dRAGONAIR
  • 1,181
  • 6
  • 8
  • OK, I now added your code, and it works, but only the 2nd time you view the UIViewController. I created a CGFloat, then I assign the height of the UITextView to the CGFloat. then I return the created CGFloat in the methode you posted above. I'll post my new code above. – David Gölzhäuser Oct 06 '13 at 18:46
  • 1st thing that you should know is that you do not set height for cell in 'cellForRowAtIndexpath' It should be done in 'heightForRowAtIndexpath' 2nd thing is that 'heightForRowAtIndexpath' is called before 'cellForRowAtIndexpath'. That mean you have to calculate the height for a cell before you populate the same cell.(The table should be informed about the height before u start populating the cell – dRAGONAIR Oct 08 '13 at 01:27
  • Note: All `UILineBreakMode*` are deprecated, replaced with `NSLineBreakBy*`. – Raptor Jun 26 '14 at 02:39
  • How do you change the width, though? – Phillip Jul 17 '14 at 23:09
  • 1
    You can make the UITableView smaller. – AdamG Jul 18 '14 at 06:59
4

I favor this solution of Jure

  • First, set constraints of textview to be pinned with its superview (cell's contentView in this case).
  • Disable textView.scrollEnabled
  • Set

    table.rowHeight = UITableViewAutomaticDimension;
    tableView.estimatedRowHeight = 44;
    

    If finally, your code not works, then use this instead

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return UITableViewAutomaticDimension;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return 44;
    }
    
  • Implement UITextViewDelegate like this:

    - (void)textViewDidChange:(UITextView *)textView {
         CGPoint currentOffset = self.tableView.contentOffset;
         [UIView setAnimationsEnabled:NO];
         [self.tableView beginUpdates];
         [self.tableView endUpdates];
         [UIView setAnimationsEnabled:YES];
         [self.tableView setContentOffset:currentOffset animated:NO];
      }
    
samthui7
  • 923
  • 2
  • 12
  • 11
3

This thread has been quite a while, but in iOS 8 UITableViewAutomaticDimension was introduced. You have to set constraints from the top to the bottom of the cell like a scroll view to make this work. But after that, just add the following code to viewDidLoad():

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 122.0

Make sure your estimated height is as close as possible to the real thing otherwise you'll get some buggy scrolling.

Rob Norback
  • 6,401
  • 2
  • 34
  • 38
2

Adding these two methods to the ViewController with UITableViewAutomaticDimension should do the trick. It has worked for me when embedding a UITextView inside of a UITableViewCell with variable length text.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewAutomaticDimension;
}

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewAutomaticDimension;
}
Stephen
  • 317
  • 1
  • 11
1

In case of UILabel subview in the UITableViewCell, I accomplished auto resize of the label just by setting the label's constraints (using storyboard, Xcode 8.3.2).

This is working since apparently the label's and the cell's default behavior is sizeToFit. Same should work for UITextView as well.

mark
  • 181
  • 6