I am implementing a UITableView in which the table cells contain several controls, such as a labels and buttons. One of the labels toward the top of the cell can have varying amounts of text, thus the entire size of each cell must vary as well. So basically, it’s the dynamic table cell problem that you see all over the place.
The de facto solution that I seem to find is this one
, which I am using, but I don’t care much for it because it comes with maintenance problems. Since I have so many other things in the cell, I have to have a lot of hardcoded heights to represent all the other controls that must be accounted for when determining the overall size of the cell and when laying everything out based on the size of the dynamic content.
Here is some code:
- (CGFloat) tableView:(UITableView*)tableView
heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
SomeDataItem* item = [self.myData objectAtIndex:indexPath.row];
CGSize constraintSize = CGSizeMake(275, MAXFLOAT);
CGSize labelSize = [item.message sizeWithFont:[UIFont systemFontOfSize:13]
constrainedToSize:constraintSize
lineBreakMode:UILineBreakModeWordWrap];
return HEADER_HEIGHT + OUTER_MARGIN + labelSize.height +
INNER_MARGIN + INNER_MARGIN + OUTER_MARGIN +
BUTTON_HEIGHT + OUTER_MARGIN;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
...
NSString* message = //some dynamic text
CGSize constraintSize = CGSizeMake(275, MAXFLOAT);
CGSize labelSize = [message sizeWithFont:[UIFont systemFontOfSize:13]
constrainedToSize:constraintSize
lineBreakMode:UILineBreakModeWordWrap];
//resize label according to amount of text
cell.messageLabel.frame = CGRectMake(cell.messageLabel.frame.origin.x,
cell.messageLabel.frame.origin.y,
cell.messageLabel.frame.size.width,
labelSize.height);
//now move controls below the dynamic label down
y = HEADER_HEIGHT + OUTER_MARGIN;
[self setView:cell.messageTextBackground yCoord:y];
y += INNER_MARGIN;
[self setView:cell.messageLabel yCoord:y];
y += labelSize.height;
[self setView:cell.anotherLabel yCoord:y];
y += OUTER_MARGIN;
[self setView:cell.someButton yCoord:y];
}
- (void) setView:(UIView*)view yCoord:(NSInteger)y
{
int x = view.frame.origin.x;
int w = view.frame.size.width;
int h = view.frame.size.height;
view.frame = CGRectMake(x, y, w, h);
}
That’s just kind of an approximation of the code to give you an idea of what I’m doing. As you can see, it’s kind of a mess. If I have to keep #define values for all the other controls beside the dynamic label. If I want to change the size of any of these controls I have to do it in the XIB file and then also remember to update the #define values. Even worse, if I want to introduce some new controls, I have to modify both the of these methods and make sure everything is still getting calculated and offset properly.
This seems pretty absurd to me, but I haven’t found any other way to doing it. If only you could get access to the table cells within tableView:heightForRowAtIndexPath, then you could just calculate everything based on the actual controls, instead of having to hard code everything. However, from what I’ve read and experienced tableView:heightForRowAtIndexPath gets called before the cell even exists so you can’t do that.
Am I off on how I am handling this or is this the only way to go?
I think you are doing the right thing. At least that is how I have done it for a while. Maintenance should be OK, you just have to do the things you mentioned.
You could perhaps cut down on your
#definestatements by using a XIB file for the cell, loading the xib file inviewDidLoadand extracting the sizes of the various elements there. You would then only have to change them in the XIB file in the future.Of course a wrapper view containing the non-changing elements could be an additional help.