Reputation: 215
I'm trying to get a label in a cell to be the right size, regardless of device or orientation. I am able to get the row height to size correctly. I am also able to set the label height correctly in cellForRowAtIndexPath
, and can check that in my logs. But, by the time it gets to willDisplayRowAtIndexPath
, the label height has changed, but only when the cell is not 320 pts wide.
Here's my code-
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"CustomCellIdentifier";
CustomInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
cell = [[CustomInfoCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:@"CustomInfoCell" owner:self options:nil];
cell = objects[0];
}
// Configure the cell...
cell.customTitleLabel.text = [_data[indexPath.row] objectForKey:t];
CGFloat labelWidth = self.view.frame.size.width-40;
NSLog(@"labelWidth:%f",labelWidth);
NSString *text = [_data[indexPath.row] objectForKey:d];//correct text
CGSize labelsize=[text sizeWithFont:cell.customDetailLabel.font constrainedToSize:CGSizeMake(labelWidth, 2000.0) lineBreakMode:cell.customDetailLabel.lineBreakMode];
NSLog(@"labelsize:%f,%f",labelsize.width,labelsize.height);
//For testing
cell.customDetailLabel.backgroundColor = [UIColor redColor];
NSLog(@"Pre: %@",cell.customDetailLabel);
cell.customDetailLabel.frame=CGRectMake(20, 22, labelWidth, labelsize.height);
cell.customDetailLabel.text = text;
NSLog(@"Post: %@",cell.customDetailLabel);
return cell;
}
In willDisplayRowAtIndexPath
I also print the label info. Here's what one row prints-
2013-03-24 18:33:44.009 Bridge Alert[57793:1e503] labelWidth:728.000000
2013-03-24 18:33:44.010 Bridge Alert[57793:1e503] labelsize:713.000000,76.000000
2013-03-24 18:33:44.010 Bridge Alert[57793:1e503] Pre: <UILabel: 0xad3eaf0; frame = (20 20; 280 21); text = 'Detail'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x17372eb0>>
2013-03-24 18:33:44.011 Bridge Alert[57793:1e503] Post: <UILabel: 0xad3eaf0; frame = (20 22; 728 76); text = 'Detail'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x17372eb0>>
2013-03-24 18:33:44.011 Bridge Alert[57793:1e503] Text set: <UILabel: 0xad3eaf0; frame = (20 22; 728 76); text = 'A bridge is considered “f...'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x17372eb0>>
2013-03-24 18:33:44.014 Bridge Alert[57793:1e503] Display:<UILabel: 0xad3eaf0; frame = (20 20; 728 190); text = 'A bridge is considered “f...'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x17372eb0>>
As you can see, by Display, the label is resized. I'm assuming the height is recalculated somehow, based on if the cell were 320 pt wide, which is the built in UITableViewCell width.
How can I get the label to size correctly?
Upvotes: 4
Views: 10218
Reputation: 3562
Just post my own simplified approach based on @Paulo, but now with adapt to storyboard constraints width and more accurate.
(Swift)
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier")!
// Use subclass of UITableViewCell if your cell is complex.
let label = cell.viewWithTag(3001) as! UILabel;
label.text = someStringArray[indexPath.row]
label.sizeToFit();
let y = label.frame.origin.y
let height = label.frame.size.height
let paddingBottom: CGFloat = 8
// add other controls/view height if any after the expand label
return y + height + paddingBottom
}
Upvotes: 0
Reputation: 2154
This is what worked for me with the minimal code as possible using AutoLayout.
Within my ViewController I added the following methods
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * yourText = //place here the text you want to calculate its size;
return 40 + [self heightForText:yourText];
}
-(CGFloat)heightForText:(NSString *)text
{
NSInteger MAX_HEIGHT = 2000;
UITextView * textView = [[UITextView alloc] initWithFrame: CGRectMake(0, 0, 320, MAX_HEIGHT)];
textView.text = text;
textView.font = [UIFont boldSystemFontOfSize:12];
[textView sizeToFit];
return textView.frame.size.height;
}
This code above basically changes UITableViewCell's size based on the size of my UILabel + a padding number which in my case I defined as 40.
After this I had to add a couple of constraints as shown below in order to make the UILabel to "stretch" as UITableViewCell does
After running it:
Note: I took different pieces of code from many threads in SO to come up with this solution. I wish I could remember all of `em to mention here
Hope it works for you guys.
Cheers
Upvotes: 19
Reputation: 533
Have you tried unchecking auto layout? This will remove the unnecessary (mostly unhelpful) Constraints.
Upvotes: 3
Reputation: 3749
In my opinion, interface builder/storyboard can be useful but often adds another unneeded layer of complexity. I would simply subclass UITableViewCell (as you are but do it purely programmatically) and set the frame of customDetailLabel using the layoutSubviews.
Upvotes: 2
Reputation: 6270
Try overriding setFrame
in your CustomInfoCell
class to include resizing the elements in your cell after you set the frame. I've included code I use to accomplish this, which is a little different from your implementation. Also, on your label I set the autoresizing mask to UIViewAutoresizingFlexibleWidth
.
Have this at the top of your file:
static void *kKVOContext; // See: http://www.dribin.org/dave/blog/archives/2008/09/24/proper_kvo_usage/
In viewDidLoad include:
[self.textLabel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:&kKVOContext];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:[UIDevice currentDevice]];
Then include:
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
//Resize elements to fit inside new frame
[self textChanged];
}
- (CGSize)textLabelSize
{
BOOL portrait = UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation);
CGFloat width = portrait ? [UIScreen mainScreen].bounds.size.width : [UIScreen mainScreen].bounds.size.height;
CGFloat maxWidth = width - 90 - TEXT_X_LEFT_PADDING;
CGSize maximumLabelSize = CGSizeMake(maxWidth, 10000);
return [self.textLabel.text sizeWithFont:self.textLabel.font
constrainedToSize:maximumLabelSize
lineBreakMode:self.textLabel.lineBreakMode];
}
- (CGFloat)cellHeight
{
CGSize expectedLabelSize = [self textLabelSize];
return (self.titleLabel.frame.origin.y+self.titleLabel.frame.size.height) + expectedLabelSize.height;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqual:@"text"] && object == self.textLabel) {
[self textChanged];
}
if(context != &kKVOContext)
{
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
- (void)orientationChanged:(NSNotification *)note
{
//Resize text on orientation change
[self textChanged];
}
- (void)textChanged
{
CGRect newFrame = self.textLabel.frame;
newFrame.size = [self textLabelSize];
if(!CGRectEqualToRect(self.textLabel.frame, newFrame))
{
self.textLabel.frame = newFrame;
}
}
Upvotes: 0
Reputation: 2411
I used to have the exact same problem, for some reason the label height changes when it actually gets displayed. I ended up using the free Sensible TableView framework, which actually manages to do the resizing correctly (it even resizes the cell if you choose to have a multiline UILabel). The framework also assigned the label's text automatically from my object's property which was really cool.
Upvotes: 1