cellForRowAtIndexPath sees cell as different height when its loaded, then reloaded? - ios

Preface: this is quite an abstract question.
I've got some cells in a UITableView that use a different image depending on the height of the cell (in 50px increments, from 50px up to 1000px high). I'm hitting a weird issue where when the app first loads, cellForRowAtIndexPath reports that the first three cells (regardless of whether or not all three are onscree) have a height of 100px, then when I scroll down and back up again, their height reverts to the correct value. There's no onscreen change of height, only the image used changes, and the value of cell.frame.size.height in my NSLog statements.
I've been logging in heightForRowAtIndexPath and cellForRowAtIndexPath, and I can verify that the heights are correct when they leave heightForRowAtIndexPath. But, when entering cellForRowAtIndexPath, those first three heights are all set uniformly to 100. I then scroll down until those three cells are off-screen, then scroll back up, and the .height value has changed to the correct value.
To summarise, my questions are:
Is there any code called between the very end of heightForRowAtIndexPath and the very beginning of cellForRowAtIndexPath that could be changing the cell.frame.size.height value of these first few cells?
Why would this issue only be occurring on the first THREE cells? And, why would they revert when reloaded a moment later? I thought the reuse identifier could be the cause, but they're still assigned that identifier when first loaded, so that shouldn't matter, should it?
EDIT: I think the 100px height is coming from the row height of the prototype cell in my storyboard, as if I change that to 101px, those first three cells are then initially at 101px.
EDIT: Here's the code I'm using to set the image, though this part is working, it's just got the wrong value to work with:
UIImage *cappedBG;
float cellHeight = cell.contentView.frame.size.height;
if (cellHeight <= 51) {
cappedBG = [[UIImage imageNamed: #"bg50.png"] resizableImageWithCapInsets: UIEdgeInsetsMake(25, 25, 25, 25) resizingMode: UIImageResizingModeStretch];
} else if (cellHeight <= 101) {
cappedBG = [[UIImage imageNamed: #"bg100.png"] resizableImageWithCapInsets: UIEdgeInsetsMake(25, 25, 25, 25) resizingMode: UIImageResizingModeStretch];
} else if (cellHeight <= 151) {
cappedBG = [[UIImage imageNamed: #"bg150.png"] resizableImageWithCapInsets: UIEdgeInsetsMake(25, 25, 25, 25) resizingMode: UIImageResizingModeStretch];
} else if (cellHeight <= 201) {
cappedBG = [[UIImage imageNamed: #"bg200.png"] resizableImageWithCapInsets: UIEdgeInsetsMake(25, 25, 25, 25) resizingMode: UIImageResizingModeStretch];
} else {
cappedBG = [[UIImage imageNamed: #"bg250.png"] resizableImageWithCapInsets: UIEdgeInsetsMake(25, 25, 25, 25) resizingMode: UIImageResizingModeStretch];
}

You just can't rely on the frame of the cell in tableView:cellForRowAtIndexPath:. The frame of the cell you configure in that method will be set after the code has returned from this method.
The values you get are not the actual heights of that particular row. You get the height from the cell that has just been dequeued. If there is no cell to dequeue the height from the cell that was just created is used. And 100 is probably the height of the cell in your storyboard/nib.
This height has nothing to do with the height of the cell that will be displayed.
Since you calculate the row height in tableView:heightForRowAtIndexPath: already, use this value.
float cellHeight = [self tableView:tableView heightForRowAtIndexPath:indexPath];
if (cellHeight <= 51) {
cappedBG = [[UIImage imageNamed: #"bg50.png"] resizableImageWithCapInsets: UIEdgeInsetsMake(25, 25, 25, 25) resizingMode: UIImageResizingModeStretch];
...

You shouldn't really be doing layout of a cell in cellForRowAtIndexPath.
The best way to do this is to create a UITableViewCell subclass with all the UI elements you need.
Then in the subclass you can override - (void)layoutSubviews.
This way you keep everything separate rather than putting EVERYTHING in the view controller.

Currently, after looking at your question, I think you are having trouble with different cell heights (when scrolled). I believe you have not given correct values in heightForRowAtIndexPath.
Following is the simple code that I have written. This code has displays 20 rows in the table and alternate row has different size.(I have used different colours to distinguish)
#pragma mark - TableView DataSource Methods
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row %2 == 0) return 50;
else return 100;
}
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *stringCellIdentifier = #"BookOutVehicleCell";
UITableViewCell *tableViewCell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:stringCellIdentifier];
if (tableViewCell == nil)
tableViewCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:stringCellIdentifier] autorelease];
if(indexPath.row %2 == 0) tableViewCell.contentView.backgroundColor = [UIColor redColor];
else tableViewCell.contentView.backgroundColor = [UIColor greenColor];
return tableViewCell;
}
#pragma mark - TableView Delegate Methods
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
This is the output.
In this case, even if we scroll the tableview up and down, rows height does not change.
Hope this helps. And sorry If I have misunderstood your question. It would be nice if you provide a code snippet. It will give us better idea about the issue.
EDIT:
As per my understanding of the issue you are getting correct heights for each row/cell, however you have used an imageview in cell which will display different images as per the height of the cell. Check if following works for you:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *stringCellIdentifier = #"BookOutVehicleCell";
UITableViewCell *tableViewCell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:stringCellIdentifier];
if (tableViewCell == nil)
tableViewCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:stringCellIdentifier] autorelease];
//Check your height here and use appropriate image
if(cell.contentView.frame.size.height <100)
{
image = "xxx.png";
}
else
if(cell.contentView.frame.size.height >=100 && cell.contentView.frame.size.height <200)
{
image = "yyy.png";
}
}
Thanks.

Related

UIImageView auto resize inside a custom UITableViewCell with varying height

I have set up a custom cell prototype inside storyboard not using autolayout and i have run into a rather vexing problem where a UIImageView i have inside the cell seems to obey constraints all of it's own when i want it to reflect the size of the cell with a bit of padding.
Here is the setup in storyboard interface builder and view mode is set to "scale to fill"
And here is what i get back, with no editing of the placement or scaling of the image, it seems very much like it's using aspect fill.
I have tried various options and none seem to work for me so far, The only other way i can think is turn of auto resizing and set the rect myself manually when the cell is created any ideas?
and my code for setting the cell
- (void)setCell:(NSDictionary*)messageData
{
[_nameLabel setText:messageData[#"name"]];
[_messageLabel setText:messageData[#"message"]];
[_nameLabel sizeToFit];
[_messageLabel sizeToFit];
UIImage *image;
if([messageData[#"direction"] boolValue])
{
image= [[UIImage imageNamed:#"bubble_left"]resizableImageWithCapInsets:UIEdgeInsetsMake(18, 6, 6, 10) resizingMode:UIImageResizingModeStretch];
}
else
{
image= [[UIImage imageNamed:#"bubble_right"]resizableImageWithCapInsets:UIEdgeInsetsMake(18, 10, 6, 6) resizingMode:UIImageResizingModeStretch];
}
[_bubbleImage setImage:image];
}
and the protocol methods
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CUIChatCellTableViewCell *cell = [_chatTableView dequeueReusableCellWithIdentifier:#"CustomCell" forIndexPath:indexPath];
NSDictionary *messageData = _testDataArray[indexPath.row];
[cell setCell:messageData];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
_customCell = [_chatTableView dequeueReusableCellWithIdentifier:#"CustomCell"];
//configure cell
NSDictionary *messageData = _testDataArray[indexPath.row];
[_customCell setCell:messageData];
//Get height of the cell
CGFloat height = [_customCell.messageLabel frame].size.height+[_customCell.nameLabel frame].size.height+20;
return height;
}
Set autoresizing properties like this, may you get help from this.
I could not get AutoResizing to work and i tried another test project from scratch and it seemed to work, so it must have been certain settings, either way i ended up having to set the height manually for each cell based on it's content anyway here is the code to do so
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
_customCell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
//configure cell
CMessageInfo *messageData = _testDataArray[indexPath.row];
[_customCell setCell:messageData];
//Get height of the cell
CGFloat height = [_customCell.messageLabel frame].size.height+[_customCell.nameLabel frame].size.height+20;
return height;
}

TableView rows get mixed up when scrolling

I am using a tableview in a UIViewController and I have subclass of UITableViewCell.
I register the cell in viewDidLoad;
- In cellForRowAtIndexPath, I use dequeueReusableCellWithIdentifier to get the cell and I reset all the labels to the right values; However, when the table scrolls, the top rows and the bottom rows get mixed up and the labels get interchanged. I don't know why this would happen when I am resetting the cells to the right values for each row. Do you have any idea why the mix up happens.
[self.myFoldersTableView registerClass:[QConnectFoldersTVCell class] forCellReuseIdentifier:QMY_FOLDERS_CELL_ID];
and
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:NSIndexPath *)indexPath
{
QConnectFoldersTVCell *cell = (QConnectFoldersTVCell *)[tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath];
NSMutableDictionary *cellDataDict = [self findCellDataAtIndexPath:indexPath];
// Configure the cell...
cell.mainLabel.text = cellDataDict[FOLDER_CELL_DICT_KEY_MAIN_TEXT];
cell.detailLabel.text = cellDataDict[FOLDER_CELL_DICT_KEY_DETAIL_TEXT];
cell.folderCellType = [cellDataDict[FOLDER_CELL_DICT_KEY_TYPE] intValue];
return cell;
}
I want to add that I have printed out the label values being set for the row and the data is right for each row. The cells are being reset to the correct data in the above function. So I don't know why something else is displayed on screen.
I found the reason for the mixup. In my tableViewCell subclass, I was using layoutSubviews to do initialization for the labels because the actual size of the cell isn't available in init but is available in layoutSubviews. Removing layoutSubviews override seems to have stopped the row mixup of data/values.
- (void)layoutSubviews
{
[super layoutSubviews];
CGSize size = self.contentView.frame.size;
CGFloat mainHeight = ((size.height * 6)/10) - 6.0;
CGFloat detailHeight = ((size.height*4)/10) - 6.0;
self.mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(8.0, 4.0, size.width - 16.0, mainHeight)];
self.detailLabel = [[UILabel alloc] initWithFrame:CGRectMake(8.0, 4.0+(self.mainLabel.frame.size.height)+4.0, size.width - 16.0, detailHeight)];
}

Can't change UITableViewCell contentView height

I have a custom uitableviewcell.
i want to change its height based on some if condition.
How can i be able to do this ?
I tried doing this in cellForRowAtIndexPath -
if (cell.newsFeedImage.image == NULL)
{
NSLog(#"NULL");
cell.uDisplayImage.frame = CGRectMake(20, 20, 20, 20);
}
The log message appears, but the frame never changes.
Please help.
try to do this in heightForRowAtIndexPath
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell.newsFeedImage.image == nil) {
cell.uDisplayImage.frame = CGRectMake(20, 20, 20, 20);
}
else return 40;
}
The height of the cell is set in the tableView datasource method
heightForRowAtIndexPath
It's a little more complicated than that.
This questions has been answered at great length here: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Access previous cell from layoutSubviews

I've created a custom UITableViewCell class. In that class I adjust the height of the cell depending on the image inside it.
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
if (self.imageView.image) {
self.frame = CGRectMake(self.frame.origin.x,self.frame.origin.y,
self.frame.size.width, self.imageView.image.size.height);
}
}
But since they are all different heights some cells overlap each other.
The first image is the same height as the default cell height, therefore the second image is fine. The second image has a larger height which makes the third image to be positioned behind it.
Can I somehow access the previous cell from the layoutSubviews and adjust the y-position from that?
By doing this in layoutSubviews instead of tableView:heightForRowAtIndexPath:, your code depends on a side effect and is trying to defeat UITableView. You should fix this in tableView:heightForRowAtIndexPath: and let the UITableView do the work for you.
Instead of fixing a constant row height in the method
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 65; //For example
}
In the case, for example, that you have an array with the names of the images ordered (imageNamesArray), you can use something like this:
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGFloat desiredImageWidth = 50.0f;
UIImage *image = [UIImage imageNamed:[imageNamesArray objectAtIndex:indexPath.row]];
CGFloat ratio = image.size.height/image.size.width;
return desiredImageWidth * ratio;
}

Why does last cell's height of UITableView get used for remaining empty cells?

My cells all have varying heights, determined by text entered by the user.
Whatever height the last cell takes on seems to determine (change) the height for all remaining blank cells.
It doesn't appear that heightForRowAtIndexPath is getting called for these remaining, empty cells, nor is CFRAIP.
Does anyone know how to fix this or why it is happening?
Put this code in viewDidLoad of the the viewController where Tableview is placed.
self.tableView.tableFooterView = [[UIView alloc] init];
I meet the problem too. And it frustrated me much. After some consideration, I think it may be a feature of tableview that it show blank cell. It's reasonable to use the last cell height for the blank cell. Otherwise, what height to choose to use?
I think it's not acceptable to show black cell. If you dislike the style like me, you can try this to get rid of blank cells:
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
or
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
//then add view for separator line by yourself
or
UITableView *tableView = [[UITableView alloc] initWithFrame:self.tableView.frame style:UITableViewStyleGrouped];
tableView.backgroundColor = self.tableView.backgroundColor;
self.tableView = tableView;
If you must show blank cell and control the height, maybe it's the simplest way/workaround to add additional cell. For example:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataArray == nil || self.dataArray.count == 0 ? 1 : self.dataArray + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.count == self.dataArray.count){ //the last cell
cell = [[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
} else {
cell = .... //as uausal
}
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.count == self.dataArray.count){
return 50;
} else {
return xxx; //as usual
}
}
Now we can control any height !.
Hope it can help you.
You can refer to the link as well: How can I modify the appearance of 'empty' cells in plain UITableView?

Resources