Calling heightForRowAtIndexPath from within cellForRowAtIndexPath? - ios

So, it seems that heightForRowAtIndexPath is called before cellForRowAtIndexPath. I'm currently using cellForRowAtIndexPath to calculate the height of each cell (they're variable heights, using a UILabel with varying amounts of text in it.
I'm puzzled as to exactly how I can set the cell's height correctly if the method that sets the height is called before the method that calculates it.
I've created a category of NSString, and another of UILabel, which working together make the UILabel the right size. The issue is that the UITableViewCell instances which contain those labels aren't being resized, so the labels hang over the end of each label, overlapping the ones beneath.
I know that what I need to do is make the height of the cell equal the height of the label, plus some extra margin space, but I'm puzzled as to how I can set the height before the label height has been calculated. Can I set the height someone without using heightForRowAtIndexPath? Or if not, could I calculate the height of the cell elsewhere? I'm currently using indexPath.row to make that happen, so doing it beforehand would be tricky. Here's the code my cellForRowAtIndexPath:
cellForRowAtIndexPath
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
FQCustomCell *_customCell = [tableView dequeueReusableCellWithIdentifier: #"CustomCell"];
if (_customCell == nil) {
_customCell = [[FQCustomCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: #"CustomCell"];
}
_customCell.myLabel.text = [[_loadedNames objectAtIndex: indexPath.row] name];
_customCell.myLabel.font = [UIFont fontWithName: #"FontA" size: 15.0f];
UIView *backView = [[UIView alloc] initWithFrame: CGRectZero];
backView.backgroundColor = [UIColor clearColor];
_customCell.backgroundView = backView;
_customCell.myLabel.frame = CGRectMake(5, 5, 265, 65);
[_customCell.myLabel sizeToFitMultipleLines];
return _customCell;
}

You need to calculate the height of the cell in heightForRowAtIndexPath, something like
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *theText=[[_loadedNames objectAtIndex: indexPath.row] name];
CGSize labelSize = [theText sizeWithFont:[UIFont fontWithName: #"FontA" size: 15.0f] constrainedToSize:kLabelFrameMaxSize];
return kHeightWithoutLabel+labelSize.height;
}
You can set some constraints on the label size by setting kLabelFrameMaxSize
#define kLabelFrameMaxSize CGSizeMake(265.0, 200.0)
Then you return the height by adding a constant height with the variable label height.
Also, to be consistent, you should use the same methodology to set the frame in cellForRowAtIndexPath instead of using sizeToFitMultipleLines
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
FQCustomCell *_customCell = [tableView dequeueReusableCellWithIdentifier: #"CustomCell"];
if (_customCell == nil) {
_customCell = [[FQCustomCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: #"CustomCell"];
}
_customCell.myLabel.text = [[_loadedNames objectAtIndex: indexPath.row] name];
_customCell.myLabel.font = [UIFont fontWithName: #"FontA" size: 15.0f];
UIView *backView = [[UIView alloc] initWithFrame: CGRectZero];
backView.backgroundColor = [UIColor clearColor];
_customCell.backgroundView = backView;
CGSize labelSize = [_customCell.myLabel.text sizeWithFont:_customCell.myLabel.font constrainedToSize:kLabelFrameMaxSize];
_customCell.myLabel.frame = CGRectMake(5, 5, labelSize.width, labelSize.height);
return _customCell;
}

You should calculate the height of the cell in heightForRowAtIndexPath method. It should be done without using any UILabel, just like heightForRowAtIndexPath implementation in JP Hribovsek's answer.
Then in cellForRowAtIndexPath do not set the frame of any subviews and do not call sizeToFitMultipleLines method of the label. Instead, implement layoutSubviews method of FQCustomCell such that the subview frames are set correctly for any possible bounds of the cell. You should't care about the text or font size in this method, just make sure the margins of the label are correct. If the height of the cell is calculated correctly in heightForRowAtIndexPath method, the size of your labels will be just right.

For every cell heightforRowAtIndexPath is called first and then CellForRowAtIndexPath . So you can calculate the height first and then fill up the data in CellForRowAtIndexPath for every cell.

Related

UITextView sizeThatFits returns different size than boundingRectWithSize

Need the required height of UITextView. sizeThatFits returns bigger, but the correct height than boundingRectWithSize. Why difference exist?
At two places I need to know the height. In cellForRowAtIndexPath and in heightForRowAtIndexPath.
I do not think it is efficient to create always a UITextView in heightForRowAtIndexPath just to know what height is required.
What workaround do you know to calculate height of a UITextView in heightForRowAtIndexPath?
I met similar problem last month for UITableView, and I use boundingRectWithSize to calculate the size, it is actually correct. I then put it into UITextView.
Some mistakes I made:
I forget to set the same font size when calculating and for UITextView
UITextView has margins, I will manually add it in heightForRowAtIndexPath and set textContainerInset to the same one.
Hope it helps you.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = indexPath.section;
NSUInteger axisIndex = section - 2;
yAxis *yAxisObj = self.yAxisInfoArray[axisIndex];
boundingRect = [yAxisObj.yAxisDescription boundingRectWithSize:CGSizeMake(self.descriptionViewWidth, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName:self.contentFont}
context:nil];
return boundingRect.size.height + TEXT_TOP_MARGIN + TEXT_BOTTOM_MARGIN;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = #"ChartDescriptionCell";
ChartDescriptionCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[ChartDescriptionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
cell.textView.bounces = NO;
cell.textView.showsHorizontalScrollIndicator = NO;
cell.textView.showsVerticalScrollIndicator = NO;
cell.textView.font = self.contentFont;
cell.textView.textColor = [UIColor colorWithHex:#"#333333"];
cell.textView.textContainerInset = UIEdgeInsetsMake(TEXT_TOP_MARGIN, -5, TEXT_BOTTOM_MARGIN, -5);
}
NSInteger section = indexPath.section;
NSUInteger axisIndex = section - 2;
yAxis *yAxisObj = self.yAxisInfoArray[axisIndex];
cell.textView.text = yAxisObj.yAxisDescription;
}
return cell;
}
boundingRectWithSize returns size for text, so you should manually provide your font.
sizeThatFits returns size of UITextView with this text inside
If you are pointing to iOS 8 and above you can use Dynamic cell height which is very easy. In case of iOS 7 you need some workaround.
Tutorial: http://www.raywenderlich.com/87975/dynamic-table-view-cell-height-ios-8-swift
Related question with nice answer: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Need to access my UITableViewCell class in UITableView's heightForRowAtIndexPath:

I need to set UITableViewCell height based on my UITextView height, but when I access cell sublcass using: CommentTableViewCell *cell = (CommentTableViewCell *)[tableView cellForRowAtIndexPath:indexPath]; in heightForRowAtIndexPath:, app crashes.
What's wrong? Isn't this the right way to access my custom cell class?
From my experience, heightForRowAtIndexPath is triggered automatically before it displays the cell. So you need to know the height of the cell before it is displayed. You can not do this in heightForRowAtIndexPath at is seems that you think that you can change it be looking at cell's text's height during heightForRowAtIndexPath.
What you need to do is create an array and store the height. You can now then use that array and extract the number inside heightForRowAtIndexPath.
Here's an example:
- (void) calculateHeight {
UILabel *tempTitle = [[UILabel alloc] init];
tempTitle.font = [UIFont fontWithName:#"your font" size:#"size"];
tempTitle.lineBreakMode = NSLineBreakByWordWrapping;
for (int i = 0; i < [yourListOfData count]; i++) {
NSString *yourText = [yourListOfData objectAtIndex:i];
CGSize textSize = [yourText
sizeWithFont:#"size"
constrainedToSize:CGSizeMake(0, "maxsize of your label that you want")
lineBreakMode:NSLineBreakByWordWrapping];
[listOfHeightPerItem addObject:[NSNumber numberWithFloat:(textSize.height + 20)]];
//+20 for padding
}
}
and then in your heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat tempHeight = [(NSNumber *)[listOfHeightPerItem objectAtIndex:indexPath.row] floatValue];
return tempHeight;
}
Please note that this isn't the most optimal way but it works... especially when your deadline was yesterday ;)
If the UITextView height depends on some string length unknown until run time, then you should follow MVC architecture and determine the height from that string as defined in some model object. You can use NSString sizeWithFont or sizeWithAttributes methods to get the text size.

dequeueReusableCellWithIdentifier with cells of different size

STEP 1 - this is working fine
I have UITableView that loads custom UITableViewCells
STEP 2 - this is kinda works
I change the UITableViewCell height so that all the data contained in the cell within a UITextView is visible
I manage to get the UItextView data and resize it by using the following code:
UITextView *dummy = [[UITextView alloc] init];
dummy.font = [UIFont systemFontOfSize:14];
dummy.text = cell.textView.text;
CGSize newSize = [dummy sizeThatFits:CGSizeMake(270.0f, 500.0f)];
//get the textView frame and change it's size
CGRect newFrame = cell.textView.frame;
newFrame.size = CGSizeMake(270.0f, fmaxf(newSize.height, 60));
cell.textView.frame = newFrame;
//resize the cell view I add +95 because my cell has borders and other stuff...
CGRect cellFrame = CGRectMake(0, 0, 320, newFrame.size.height+95);
cell.cellView.frame = cellFrame;
I then manage to set the UITableViewCell height using the delegate function heightForRowAtIndexPath:
Now when I run the code and scroll up and down the table cells the behavious isn't always as expected... i.e the cell size isn't always the right size, but if I scroll up and down it sometimes loads up the right size again.
I am thinking that perhaps the dequeueReusableCellWithIdentifier is the issue
cellIdentifier = #"tableCell";
ctTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
Since I am recycling cells of different size with the same identifier...
Can anybody give me some guidelines on how best to solve this issue?
Thanks!!!
I would start by adding UITextView dynamically inside cellForRowAtIndexPath method. assumptions(data array contains the content to be displayed inside cell)
// Defines
#define CELL_TEXTVIEW_WIDTH = 320
#define CELL_TEXTVIEW_HEIGHT = 9999
#define PADDING = 5
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"tableCell";
ctTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if(cell == nil){
cell = [[tableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *_data = [data objectAtIndex:indexPath.row];
CGSize _data_contentsize = [_data sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(CELL_TEXTVIEW_WIDTH, CELL_TEXTVIEW_HEIGHT) lineBreakMode:NSLineBreakModeWordWrap];
UITextView *dummy=[[UITextView alloc] initWithFrame:CGRectMake(5, 5, 290, _data_contentsize +(PADDING * 2))];
// add Font and text color for your textview here
[cell.contentView addSubview:dummy];
return cell;
}
After this calculate the height of the cell under heightForRowAtIndexPath method.
have you implemented following method ?
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
you should set height for each row , if they are different from each other

UITableView with UILabel SizeToFit get mess when Scrolling

Hi everyone I have problem with my tableview, i make Cell with uilabel with SizeToFit and then calculate the UILabel Height for set the cell Height everything work well except when i scrolling my tableView the text get weird like one char per line:
My TableViewVell Method is:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
UILabel *label = (UILabel *)[cell viewWithTag:1];
label.text = [someArray objectAtIndex:indexPath.row];
// Set the UILabel to fit my text;
messageLabel.lineBreakMode = UILineBreakModeWordWrap;
messageLabel.numberOfLines = 0;
[messageLabel sizeToFit];
return cell;
}
The Cell Height stay in the correct Size the problem only with the UILabel ...
this the result when i load the view:
First screenshot
And this is after i start scroll the TableView:
Second screenshot
Any Help???
The method sizeToFit respects the width of the frame and adjust the height. And table view cell reuse the cells. Combining those two behaviors can cause the issue you described.
One of your label, which is very short in content (e.g. in the 4th cell with the word "jio"), is resized with sizeToFit and the resulting width is smaller than the original width you intended.
After that, table view reuse the cell and sizeToFit still respects the small width calculated from previous call of sizeToFit.
Solutions:
1) set the width of the frame to your original intended label width everytime before you call sizeToFit:
CGRect frame = messageLabel.frame;
frame.size.width = 100.0; //you need to adjust this value
messageLabel.frame = frame;
2) use your row height calculation and set the frame height instead. No need to use sizeToFit.
you should probably subclass your cell and clean the UILabel on
override func prepareForReuse() {
super.prepareForReuse()
self.label.text = nil
}
This might help
http://www.cimgf.com/2009/09/23/uitableviewcell-dynamic-height/
The approach in given reference and solution provided by #Verbumdei is nearby same.
If you are not using autolayout, this can also be solved by adding a category on UIView
public func sizeToFit(constrainedSize: CGSize) -> CGSize {
var newSize = sizeThatFits(constrainedSize)
newSize.width = min(newSize.width, constrainedSize.width)
newSize.height = min(newSize.height, constrainedSize.height)
self.size = newSize
return newSize
}
The idea is to use sizeThatFits with a constrained size to determine the size to adopt and then use the min expected width/height.
Then you just have to do something like
yourLabel.sizeToFit(self.contentView.size) // or
yourLabel.sizeToFit(CGSize(width: maxWidth, height: self.contentView.size.height))
You can set preferred Width property of label. That worked for me
preferred Width

UILabels not resizing in UITableView

I have a UITableView that uses prototype cells. The cells have a custom class called dataCell. The custom cells also have three UILabels:idLabel, contLabel, and expLabel. The cells properly resize based on the amount of text in expLabel; however, I cannot get the label itself to resize. Some labels resize when I scroll down; however, they also revert to showing only two lines and omitting text when I scroll back up. Here is my code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
dataCell *cell = (dataCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// the rest of your configure cell
// First Cell Label
[cell.idLabel setText:[idData objectAtIndex:indexPath.row]];
cell.idLabel.numberOfLines = 0;
// Second Cell Label
[cell.contLabel setText:[conData objectAtIndex:indexPath.row]];
cell.contLabel.numberOfLines = 0;
// Third Cell Label
[cell.expLabel setText:[expData objectAtIndex:indexPath.row]];
cell.expLabel.numberOfLines=0;
CGSize expectedLabelSize = [cell.expLabel.text sizeWithFont:cell.expLabel.font constrainedToSize:CGSizeMake(220, FLT_MAX) lineBreakMode:cell.expLabel.lineBreakMode];
cell.expLabel.frame=CGRectMake(cell.expLabel.frame.origin.x, cell.expLabel.frame.origin.y, expectedLabelSize.width, expectedLabelSize.height);
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {
dataCell *cell = (dataCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
CGSize maximumLabelSize = CGSizeMake(220, FLT_MAX);
[cell.expLabel setText:[expData objectAtIndex:indexPath.row]];
CGSize expectedLabelSize = [cell.expLabel.text sizeWithFont:cell.expLabel.font constrainedToSize:maximumLabelSize lineBreakMode:cell.expLabel.lineBreakMode];
if (expectedLabelSize.height<43) {
expectedLabelSize.height=43;
}
return expectedLabelSize.height; }
Any help would be much appreciated
If you are using a storyboard and a UITableViewCell then you can just change the auto resizing mask, but if you are doing it programmatically then you will have to set the calculate the text width and height and reset the frame of the labels,
UILabel* label = [[UILabel alloc] init];
UIFont* font = label.font;
CGSize maxContentSizeForText = CGSizeMake(maxTextWidth, maxTextHeight);
CGSize stringTextSize = [string sizeWithFont:font constrainedToSize:maxContentSizeForText lineBreakMode:NSLineBreakByWordWrapping];
[label setFrame:CGRectMake(xPosition, yPosition, stringTextSize.width, stringTextSize.height);
[label setNumberOfLines:1000];
your label is probably a property from a xib file or storyboard, and the number of lines is just saying that you want the label to get really really big, since we can't say "infinite" i just generally use 1000 indicating 1000 lines of text maximum

Resources