Dynamic table cell height - ios

Using the storyboard, I've created a custom cell for my table view, I've also created a custom class for it with all my properties.
Now, what would be the best way in making the cells height dynamic, where is the best way to do this?
Should I do this in the custom class for the cell? Or in my table view controller?
I imagine it would make more sense to do this in the custom class, but then how should I do this?
Once a specific label is filled in, it should change the height of the cell

You cannot change the height of the cell from your custom drawing class.
You can do this in the viewController that has the UITableView only.
Either by specifying a hardcoded row height for all cells, or by using the
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
and specifying a height for the cells here.
If you want to have different heights for the cells, you should check the indexpath.row property and return the desired height value.
In case you want to change the height of an already drawn in screen cell, you will have to reload that cell to reflect the change using this:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation

Use the following code
-(CGFloat)heightForText:(NSString *)str width:(int)width font:(UIFont *)font lineBreakMode:(NSLineBreakMode) lineBreakMode
{
CGSize textSize;
textSize = [str boundingRectWithSize:CGSizeMake(width, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : font} context:nil].size;
return textSize.height;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView == self.yourTable)
{
NSMutableDictionary *dict = [self.yourArray objectAtIndex:indexPath.row] ;
return [self heightForText:[dict valueForKey:#"reviewDescription"] width:300 font:[UIFont fontWithName:kAppRegularFont size:15.0] lineBreakMode:0]+75;
}
return 70;
}

Set your view controller to be the delegate for the table view and then implement the following UITableViewDelegate method in the view controller:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

Add these line into your "viewDidLoad" method
self.tableView.estimatedRowHeight = your height here
self.tableView.rowHeight = UITableViewAutomaticDimension;
Don't set default hight into delegate
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
}

Related

custom cell height from UIImageView size?

I have a UIImageView inside a custom cell. If there is no image to display in cell then cell height should automatically decrease.(ImageView Height should be zero in that case). How to specify constraint for this in xib ?
Your UITableView data source should represent the existence of this image.
You should use the following delegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.dataSource[indexPath.rox].imageExists) {
return 50.0; // Change to your value with image
}
return 10.0; // Change to your value without image
}
Your UITableViewDelegate should implement tableView:heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// add your condition here if image exit or not and set cell size dynamically
return [indexPath row] * 20;
}
You will probably want to use NSString's sizeWithFont:constrainedToSize:lineBreakMode: method to calculate your row height rather than just performing some silly math on the indexPath.
Here is good sample code for dynamic size cell.

Resizing a cell in a TableView

Im trying to make some of my cells bigger in height. i tried using
CGRect rect = cell.frame;
NSLog(#"before height: %f",rect.size.height);
rect.size.height +=20;
cell.frame = rect;
NSLog(#"AFTER height: %f",cell.frame.size.height);
in
cellForRowAtIndexPath
and
willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath
*)indexPath
The log shows that the values have changed but it doesnt show any change in the simulator.
Thanks for the help
use tableView:heightForRowAtIndexPath method.
Apple docs clearly explains what to do. UITableView class reference
Every tableView has a delegate property.Set it to your viewController and implement above method. Its signature is
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
So, based on indexPath, return whatever the height you desire.
If you want constant height for all rows, you can use rowHeight property of UITableView.
Use heightForRowAtIndexPath of UITableViewDelegate. Example:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return indexPath.row == _pages.count - 1 ? 408 : 450;
}
To make some cells bigger, you should implement the method tableView:heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (<SOMETHING>) {
return height1;
} else {
return height2;
}
}
tableView:cellForRowAtIndexPath: is for configuring the content of the cell that the tableView needs to display.
Note that tableView:heightForRowAtIndexPath: has performance implications if you have a really large table (1000+ entries), this method is called on every row when the table view displays.

Table Cell not displaying correctly

I've designed a custom table cell as follows:
However, it is rendering as follows:
as you see, the second label is going in the next cell. Any idea how can I solve this issue and make the cell appear as I designed it?
You need to implement UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
This method:
Asks the delegate for the height to use for a row in a specified
location.
Give a minimum height for each row here. Also if different row have different height according to the content, you have to calculate the height according to the content in that case. Something like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *stringText=[array objectAtIndex: indexPath.row];
CGSize size = [stringText sizeWithFont:[UIFont fontWithName: "yourFont" size: fontSize] constrainedToSize:maxCGSize];
return labelSize.height + padding;
}

Set row height of a UITableView according to the cell in that row

I want to adjust the row height of a UITableView according to the cell in that row.
Initially, I tried to use
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
However, the problem is, when the table is loading, the calling flow is seemed to be:
First call:
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Then call:
(UIViewTableCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
which means, before my cell is generated, I have to tell the table the height of its row.
However, what I want is exactly the opposite, i.e., after my cell is generated, I tell the table the height of this rows.
Is there any method to achieve this?
Make an array of heights for every row based on the data from tableView: cellForRowAtIndexPath: and in the tableView heightForRowAtIndexPath: just take the height value from that array.
Declare in your implementation file:
NSMutableArray *heights;
In viewDidLoad: initialise it:
heights = [NSMutableArray array];
In tableView: cellForRowAtIndexPath: set the height for each row:
[heights addObject:[NSNumber numberWithFloat:HEIGHT]];
And in the tableView heightForRowAtIndexPath: return required height:
return [heights objectAtIndex:indexPath.row];
This should work because tableView: cellForRowAtIndexPath: is called for every cell and then for every cell tableView heightForRowAtIndexPath: is called.
may be the only solution is make a variable into the .h file like
#property (nonatomic) int height
initialise it into the viewDidLoad like
self.height = 40;
and then return this variable into the
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return self.height;
}
then in your tableView: cellForRowAtIndexPath: update this variable like your new height and then reload the [self.yourTable reloadData] in your viewDidAppear

Dynamic UITableView Cell Height Based on Contents

I have a UITableView that is populated with custom cells (inherited from UITableViewCell), each cell contains a UIWebView that is automatically resize based on it's contents. Here's the thing, how can I change the height of the UITableView cells based on their content (variable webView).
The solution must be dynamic since the HTML used to populate the UIWebViews is parsed from an ever changing feed.
I have a feeling I need to use the UITableView delegate method heightForRowAtIndexPath but from it's definition:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
;//This needs to be variable
}
I can't access the cell or it's contents. Can I change the height of the cell in cellForRowAtIndexPath?
Any help would be grand. Thanks.
Note
I asked this question over 2 years ago. With the intro of auto layout the best solution for iOS7 can be found:
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
and on iOS8 this functionality is built in the SDK
This usually works pretty well:
Objective-C:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
return UITableViewAutomaticDimension;
}
The best way that I've found for dynamic height is to calculate the height beforehand and store it in a collection of some sort (probably an array.) Assuming the cell contains mostly text, you can use -[NSString sizeWithFont:constrainedToSize:lineBreakMode:] to calculate the height, and then return the corresponding value in heightForRowAtIndexPath:
If the content is constantly changing, you could implement a method that updated the array of heights when new data was provided.
self.tblVIew.estimatedRowHeight = 500.0; // put max you expect here.
self.tblVIew.rowHeight = UITableViewAutomaticDimension;
I tried many solutions, but the one that worked was this, suggested by a friend:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int height = [StringUtils findHeightForText:yourLabel havingWidth:yourWidth andFont:[UIFont systemFontOfSize:17.0f]];
height += [StringUtils findHeightForText:yourOtherLabel havingWidth:yourWidth andFont:[UIFont systemFontOfSize:14.0f]];
return height + CELL_SIZE_WITHOUT_LABELS; //important to know the size of your custom cell without the height of the variable labels
}
The StringUtils.h class:
#import <Foundation/Foundation.h>
#interface StringUtils : NSObject
+ (CGFloat)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font;
#end
StringUtils.m class:
#import "StringUtils.h"
#implementation StringUtils
+ (CGFloat)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font {
CGFloat result = font.pointSize+4;
if (text) {
CGSize size;
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
size = CGSizeMake(frame.size.width, frame.size.height+1);
result = MAX(size.height, result); //At least one row
}
return result;
}
#end
It worked perfectly for me. I had a Custom Cell with 3 images with fixed sizes, 2 labels with fixed sizes and 2 variable labels.
The big problem with cells with dynamic height in iOS is that the table vc must calculate and return a height of each cell before the cells are drawn. Before a cell is drawn, though, it doesn't have a frame and thus no width. This causes a problem if your cell is to change its height based on, say, the amount of text in the textLabel, since you do not know its width.
A common solution that I've seen is that people define a numeric value for the cell width. This is a bad approach, since tables can be plain or grouped, use iOS 7 or iOS 6 styling, be displayed on an iPhone or iPad, in landscape or portrait mode etc.
I struggled with these issues in an iOS app of mine, which supports iOS5+ and both iPhone and iPad with multiple orientations. I needed a convenient way to automate this and leave the logic out of the view controller. The result became a UITableViewController sub class (so that it can hold state) that supports default cells (Default and Subtitle style) as well as custom cells.
You can grab it at GitHub (https://github.com/danielsaidi/AutoSizeTableView). I hope it helps those of you who still struggle with this problem. If you do check it out, I'd love to hear what you think and if it worked out for you.
Here is code that I used for dynamic cell height when fetching tweets from twitter and then storing them in CoreData for offline reading.
Not only does this show how to get the cell and data content, but also how to dynamically size a UILabel to the content with padding
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Tweet *tweet = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString* text = tweet.Text;
TweetTableViewCell *cell = (TweetTableViewCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath];
//Set the maximum size
CGSize maximumLabelSize = cell.tweetLabel.frame.size;
CGPoint originalLocation = cell.tweetLabel.frame.origin;
//Calculate the new size based on the text
CGSize expectedLabelSize = [text sizeWithFont:cell.tweetLabel.font constrainedToSize:maximumLabelSize lineBreakMode:cell.tweetLabel.lineBreakMode];
//Dynamically figure out the padding for the cell
CGFloat topPadding = cell.tweetLabel.frame.origin.y - cell.frame.origin.y;
CGFloat bottomOfLabel = cell.tweetLabel.frame.origin.y + cell.tweetLabel.frame.size.height;
CGFloat bottomPadding = cell.frame.size.height - bottomOfLabel;
CGFloat padding = topPadding + bottomPadding;
CGFloat topPaddingForImage = cell.profileImage.frame.origin.y - cell.frame.origin.y;
CGFloat minimumHeight = cell.profileImage.frame.size.height + topPaddingForImage + bottomPadding;
//adjust to the new size
cell.tweetLabel.frame = CGRectMake(originalLocation.x, originalLocation.y, cell.tweetLabel.frame.size.width, expectedLabelSize.height);
CGFloat cellHeight = expectedLabelSize.height + padding;
if (cellHeight < minimumHeight) {
cellHeight = minimumHeight;
}
return cellHeight;
}
Also i think such an algorithm will suit you:
1) in cellForrowAtIndexPath you activate your webviews for loading and give them tags equal to indexPath.row
2) in webViewDidFinishLoading you calculate the height of the content in the cell, and compose a dictionary with keys and values like this: key= indexPath.row value = height
3)call [tableview reloadData]
4) in [tableview cellForRowAtIndexPath:indexPath] set proper heights for corresponding cells
This is one of my nice solution. it's worked for me.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell.textLabel.text = [_nameArray objectAtIndex:indexPath.row];
cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
We need to apply these 2 changes.
1)cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
2)return UITableViewAutomaticDimension;
In Swift 4+ you can set it dinamic
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
I always implement this in all my cells in a super cell class because for some reason UITableViewAutomaticDimension doesn't work so well.
-(CGFloat)cellHeightWithData:(id)data{
CGFloat height = [[self contentView] systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
[self fillCellWithData:data]; //set the label's text or anything that may affect the size of the cell
[self layoutIfNeeded];
height = [[self contentView] systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height+1; //must add one because of the cell separator
}
just call this method on your -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPathusing a dummy cell.
note: this works only with autolayout, but it also works with ios 7 and later.
pd: don't forget to check the checkbox on the xib or storyboard for "preferred width explicit" and set the static width (on the cmd + alt + 5 menu)
Swift
Use custom cell and labels. Set up the constrains for the UILabel. (top, left, bottom, right) Set lines of the UILabel to 0
Add the following code in the viewDidLoad method of the ViewController:
tableView.estimatedRowHeight = 68.0
tableView.rowHeight = UITableViewAutomaticDimension
// Delegate & data source
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
I had very large test in UILabel. Above all fail to work, then i create category for string as below and got the exact height
- (CGFloat)heightStringWithEmojifontType:(UIFont *)uiFont ForWidth:(CGFloat)width {
// Get text
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), (CFStringRef) self );
CFIndex stringLength = CFStringGetLength((CFStringRef) attrString);
// Change font
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef) uiFont.fontName, uiFont.pointSize, NULL);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, stringLength), kCTFontAttributeName, ctFont);
// Calc the size
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CFRange fitRange;
CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(width, CGFLOAT_MAX), &fitRange);
CFRelease(ctFont);
CFRelease(framesetter);
CFRelease(attrString);
return frameSize.height + 10;}

Resources