6

I have a UITableView which is populated by text and images from a JSON file. The TableView Cell is currently sizing correctly for "posts" that do not contain many line breaks in the text however I cannot get it to calculate the correct height for "posts" with 4 or 5 line breaks.

Code for getting height:

-(float)height :(NSMutableAttributedString*)string
{
  NSString *stringToSize = [NSString stringWithFormat:@"%@", string];

  CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);

  CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:contraint lineBreakMode:NSLineBreakByWordWrapping];

  return size.height;
}

How do I calculate the correct size while allowing for line breaks and white space?

EDIT

The Rest of the method,

Inside of TableView CellForRow:

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *row = [NSString stringWithFormat:@"%i", indexPath.row];

float postTextHeight = [self height:postText];

NSString *height = [NSString stringWithFormat:@"%f", heightOfPostText + 70];

[_cellSizes setObject:height forKey:row];
}

And the height of Table Cell:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *imageHeightString = [NSString stringWithFormat:@"%@", [_cellSizes objectForKey:indexPath.row]];

float heightOfCell = [imageHeightString floatValue];

 if (heightOfCell == 0) {
    
    return 217;
};

return heightOfCell + 5;
}
Community
  • 1
  • 1
Taylor Abernethy Newman
  • 1,162
  • 1
  • 11
  • 22
  • In your -tableView:heightForRowAtIndexPath method, where are you getting indexRow from in the first line? Did you mean indexPath.row? – Matt Tang Oct 07 '13 at 01:13
  • Also, like Timothy mentioned, you'll need to make sure that the UILabel you're using has its numberOfLines property set to 0. – Matt Tang Oct 07 '13 at 01:18
  • Hi guys, I have added the numberOfLines and SizeToFit... also corrected the question to "indexPath.row". It still is not giving me the right height.. However is _CellSizes array is is giving me the right value for the height. – Taylor Abernethy Newman Oct 07 '13 at 03:52
  • Your code seems like it should work. Any chance your table view is the grouped style? The one with multiple sections? Also a screenshot of how your table view looks could be helpful. – Matt Tang Oct 07 '13 at 04:18

4 Answers4

16

better u need to calculate the height first, don't include the height calculation part in method:

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

Better to calculate it in method:

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

since u are getting the data from json it is easy for u to calculate in the "heightForRowAtIndexPath" method.

follwing code will give the example to calculate height of text change it ur requirement. hopee this helps u :)

// i am using an array 
- (void)viewDidLoad
{
   [super viewDidLoad]; 
   // Do any additional setup after loading the view, typically from a nib.

   UIFont *labelFont = [UIFont fontWithName:@"Noteworthy-Bold" size:20];
   NSDictionary *arialdict = [NSDictionary dictionaryWithObject:labelFont forKey:NSFontAttributeName];

   NSMutableAttributedString *message = [[NSMutableAttributedString alloc] initWithString:@"this is just the sample example of how to calculate the dynamic height for tableview cell which is of around 7 to 8 lines. you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];

   array = [NSMutableArray arrayWithObjects:message, nil];
   NSMutableAttributedString *message_1 = [[NSMutableAttributedString alloc] initWithString:@"you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];
  [array addObject:message_1];   
}

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 {
     return 2;
 }

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 {
     return 2;
 }


 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   UITableViewCell *Cell = [self.aTableView dequeueReusableCellWithIdentifier:@"cell"];
   if(Cell == nil)
    {
       Cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
     }

     //dont include the height calculation part hear, becz heights are already set for all the cell 
    [Cell.textLabel sizeToFit];
    Cell.textLabel.attributedText = [array objectAtIndex:indexPath.row]; // dont calculate height hear it will be called after "heightForRowAtIndexPath" method
    Cell.textLabel.numberOfLines = 8;
    return Cell;
 }

  // put ur height calculation method i took some hardcoded values change it :)
  -(float)height :(NSMutableAttributedString*)string
 {
    /*
      NSString *stringToSize = [NSString stringWithFormat:@"%@", string]; 
  // CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);
     CGSize maxSize = CGSizeMake(280, MAXFLOAT);//set max height //set the constant width, hear MAXFLOAT gives the maximum height

     CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:20.0f] constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
return size.height; //finally u get the correct height
    */
     //commenting the above code because "sizeWithFont: constrainedToSize:maxSize: lineBreakMode: " has been deprecated to avoid above code use below

    NSAttributedString *attributedText = string;
    CGRect rect = [attributedText boundingRectWithSize:(CGSize){225, MAXFLOAT}
                           options:NSStringDrawingUsesLineFragmentOrigin
                              context:nil];//you need to specify the some width, height will be calculated
    CGSize requiredSize = rect.size;
    return requiredSize.height; //finally u return your height
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
 {
     //whatever the height u need to calculate calculate hear only    
     CGFloat heightOfcell = [self height:[array objectAtIndex:indexPath.row]];
     NSLog(@"%f",heightOfcell);
     return heightOfcell;
 }

Hope this helps u :)

For SWIFT version

class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate 
{

var messageArray:[String] = [] //array to holde the response form son for example 
override func viewDidLoad()
{
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    messageArray = ["One of the most interesting features of Newsstand is that once an asset downloading has started it will continue even if the application is suspended (that is: not running but still in memory) or it is terminated. Of course during while your app is suspended it will not receive any status update but it will be woken up in the background",
                    "In case that app has been terminated while downloading was in progress, the situation is different. Infact in the event of a finished downloading the app can not be simply woken up and the connection delegate finish download method called, as when an app is terminated its App delegate object doesn’t exist anymore. In such case the system will relaunch the app in the background.", 
                    " If defined, this key will contain the array of all asset identifiers that caused the launch. From my tests it doesn’t seem this check is really required if you reconnect the pending downloading as explained in the next paragraph.",
                    ]
}

func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
    return 1;
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return messageArray.count;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("CELL") as? UITableViewCell;
    if(cell == nil)
    {
        cell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier: "CELL")
        cell?.selectionStyle = UITableViewCellSelectionStyle.None
    }
    cell?.textLabel.font = UIFont.systemFontOfSize(15.0)
    cell?.textLabel.sizeToFit()
    cell?.textLabel.text = messageArray[indexPath.row]
    cell?.textLabel.numberOfLines = 0
    return cell!;
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
    var height:CGFloat = self.calculateHeightForString(messageArray[indexPath.row])
    return height + 70.0
}

func calculateHeightForString(inString:String) -> CGFloat
{
    var messageString = inString
    var attributes = [UIFont(): UIFont.systemFontOfSize(15.0)]
    var attrString:NSAttributedString? = NSAttributedString(string: messageString, attributes: attributes)
    var rect:CGRect = attrString!.boundingRectWithSize(CGSizeMake(300.0,CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil )
    var requredSize:CGRect = rect
    return requredSize.height  //to include button's in your tableview

 }
}
Shankar BS
  • 8,394
  • 6
  • 41
  • 53
1

@Shan had a good answer but it didn't entirely worked for me.

This is the code I used for calculating the cell height

-(float)height :(NSMutableAttributedString*)string
{
    CGRect rect = [string boundingRectWithSize:(CGSize){table.frame.size.width - 110, MAXFLOAT} options:NSStringDrawingUsesLineFragmentOrigin context:nil];
    return rect.size.height;
}

I do the -110 because that will give equal space at sides top and bottom.

Hope this helps.

Kevin van Mierlo
  • 9,554
  • 5
  • 44
  • 76
  • ya i choose some hardcoded value, "boundingRectWithSize" u can use as u did in ur code. in the above i just give an example – Shankar BS Dec 12 '13 at 04:09
0

Implement this table view delegate method:

-tableView:heightForRowAtIndexPath:

https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:

You'll call your method for determining the height and return that value with some extra padding if you wanted.

Matt Tang
  • 1,297
  • 11
  • 17
-1

The approach I recommend is to set the text of an actual label and get the required height by calling sizeToFit. For this to work, you've got to set the label's numberOfLines property to 0 and set the desired maximum width.

When using this technique with table views, you can use the prototype cell method discussed here to calculate height using an actual cell.

Community
  • 1
  • 1
Timothy Moose
  • 9,895
  • 3
  • 33
  • 44