I am overloading the delegate method -tableView:heightForRowAtIndexPath: and using -sizeWithFont:constrainedToSize: to programmatically set the height of the cell, based on the text in that cell:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case(kAboutViewOverviewSection): {
switch (indexPath.row) {
case(kAboutViewOverviewSectionRow): {
NSString *text = NSLocalizedString(#"kAboutViewOverviewSectionFieldText", #"");
CGSize s = [text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(280, 500)];
return s.height + 13;
}
default: {
break;
}
}
break;
}
default: {
break;
}
}
return 44.0;
}
This works when the table is first drawn. However, when I change the orientation of the device, from portrait to landscape, the cell height does not change.
I tried overriding the -shouldAutorotateToInterfaceOrientation: method in my table view controller:
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.tableView reloadData];
return YES;
}
This should reload the table data and (presumably) redraw the table cell views. But this does not have any effect.
Is there a way to force a cell to redraw with a new height, when the device is rotated?
EDIT: I have tried the following, to no effect:
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.tableView reloadData];
}
Here is what the application looks like in portrait orientation:
Here is what the application looks like in landscape orientation:
There is a gap between the top and bottom edges of the content.
EDIT 2:
Adjusting the size parameter via the table frame's width helped fix this display issue:
CGSize s = [text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake([tableView frame].size.width,500)];
It's less than optimal, but the simplest solution is to call -reloadData on the table.
A better solution would be to call -beginUpdates, -deleteRowsAtIndexPaths:withRowAnimation:, -insertRowsAtIndexPaths:withRowAnimation:, and -endUpdates or simply -reloadSections:withRowAnimation: if targeting 3.0 only. This will add animation.
Edit: And you will also need a proper tableView:heightForRowAtIndexPath:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize textSize = [[self textForRowAtIndexPath:indexPath] sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake([tableView frame].size.width - 20, 500)];
return textSize.height + 13.0f;
}
(where textForRowAtIndexPath: is a method that returns the cell's text)
Related
I have a UITableView where each cell contains one UIView in its contentView, let's call it V.
V also has subViews, one UIImageView and UILabel.UIImage is just a white rounded rectangle
I want my cell (along with UIImageView) to expand and shrink when selected. I added some code to didSelectRowAtIndexPath and heightForRowAtIndexPath methods.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber * key = [NSNumber numberWithInt:indexPath.row];
UITableViewCell *cell = [cells objectAtIndex:indexPath.row];
UIView * v = [[[cell contentView] subviews] objectAtIndex:0];
UIImageView * image = [[v subviews] objectAtIndex:0];
_tappedIndex = [key intValue];
_expanded = [_tappedCells objectForKey:key] == nil;
NSLog(#"view's old params: %#", [GGStackPanel printFrameParams:v]); //prints out the frame params
NSLog(#"image's old params: %#", [GGStackPanel printFrameParams:image]);
if (_expanded)
{
[_tappedCells setObject:key forKey:key];
v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y, v.frame.size.width, v.frame.size.height*1.5);
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, image.frame.size.width, v.frame.size.height + 73);
}
else
{
[_tappedCells removeObjectForKey:key];
v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y, v.frame.size.width, v.frame.size.height/1.5 );
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, image.frame.size.width, v.frame.size.height - 113);
}
NSLog(#"view's new params: %#", [GGStackPanel printFrameParams:v]);
NSLog(#"image's new params: %#", [GGStackPanel printFrameParams:image]);
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tv heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *v = [_cells objectAtIndex:indexPath.row];
CGFloat height = v.frame.size.height;
if (indexPath.row == _tappedIndex)
{
if (_expanded)
{
height = v.frame.size.height * 1.5;
}
else
{
height = v.frame.size.height/1.5;
}
}
return height;
}
The cell frame and V's are expanding, I logged their frame parameters. But the imageView always stays the same, even if you change its frame at each cell selection. Its frame changes while in didSelectRowAtIndexPath method, but when you tap on the same cell again, it says that its frame hasn't change from last time.
If I put another UIView instead of an imageView, it expands and shrinks with its parent View and cell. Thus, I came up with really odd solution: cut the rounded rectangle, leave narrow top and bottom parts as imageView. Then put a blank UIView in the middle, with same color as a rectangle. Now my "rectangle" is expanding, because the blank UIView expands and those two imageViews are shifting up and down. I don't like this solution, because UI gets messed up if you turn your phone to landscape mode, or try to expand a cell from landscape mode and go back to portrait.
Any comments, suggestions?
UIImage view has the same size as its image if you use initWithImage:. I assume that you're adding the image in IB, so I'm guessing that IB uses that method to fill the image view. If you can't add the image after the expansion (with setImage which will cause the image to fill the size of the image view), then I would just use a UIView with rounded corners. You don't need to do the slicing, just use a layer with rounded corners. This is what I've done to make a cell have a grouped look, by putting a rounded white rectangle inside a cell. This is the init method for the subview:
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
self.backgroundColor = [UIColor whiteColor];
CALayer *bgLayer = self.layer;
[bgLayer setMasksToBounds:YES];
[bgLayer setCornerRadius:8];
}
return self;
}
Be sure to import the QuartzCore framework if you do this.
In my custom table view cell subclass, the location of one of the textlabel depends on the content of an ivar (NSString). (i.e: if the NSString is the empty string, the location of the textlabel's frame is different).
The position if updated as follow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
customOverlayCell *myCell = [self.tableView dequeueReusableCellWithIdentifier:#"CustomOverlayCell"];
if ([buildingFName isEqual:#""])
{
CGRect titleLabelFrame = myCell.titleLabel.frame;
titleLabelFrame.origin.y = 45;
[myCell.titleLabel setFrame:titleLabelFrame];
}
return myCell;
}
I have removed parts of code that weren't relevant.
The result is that the layout of the first cells that appear on the screen are properly updated, but the layout of the views that appear after scrolling down aren't updated.
Am I not using dequeueReusableCellWithIdentifier properly? Or is anything else wrong?
Edit:
Solution from EJV:
CGRect titleLabelFrame = myCell.titleLabel.frame;
if ([buildingFName isEqual:#""])
{
titleLabelFrame.origin.y = 45;
} else {
titleLabelFrame.origin.y = 37;
}
[myCell.titleLabel setFrame:titleLabelFrame];
If the frame of the title label is dynamic, then when you dequeue a cell from the table view, the frame could be in either of the two states (when buildingFName is empty and when it has characters). You need to make sure that you set the frame for when buildingFName is not empty. That way, the title label's frame will always be set correctly. So, you need code like this:
CGRect titleLabelFrame = myCell.titleLabel.frame;
if ([buildingFName isEqual:#""])
{
titleLabelFrame.origin.y = 45;
} else {
// Change titleLabelFrame
}
[myCell.titleLabel setFrame:titleLabelFrame];
I'm afraid, it would require subclassing the cell and implementing [UITableViewCell layoutSubviews] to properly lay out your subviews of the cell. This is how I did something similar for a switch table view cell:
- (void)layoutSubviews
{
CGFloat const ESCFieldPadding = 10.0f;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
// call super layout
[super layoutSubviews];
// obtain widths of elements
CGFloat contentWidth = self.contentView.frame.size.width;
CGFloat contentHeight = self.contentView.frame.size.height;
CGFloat switchWidth = self.switchView.frame.size.width;
CGFloat switchHeight = self.switchView.frame.size.height;
CGFloat labelWidth = contentWidth - (4 * ESCFieldPadding) - switchWidth;
// correctly position both views
self.textLabel.frame = CGRectMake(ESCFieldPadding, 0.0f,
labelWidth, contentHeight);
// it is needed to explicitly resize font as for some strange reason,
// uikit will upsize the font after relayout
self.textLabel.font = [UIFont boldSystemFontOfSize:[UIFont labelFontSize]];
CGRect switchFrame = self.switchView.frame;
switchFrame.origin = CGPointMake(contentWidth - ESCFieldPadding - switchWidth,
(contentHeight / 2) - (switchHeight / 2));
self.switchView.frame = CGRectIntegral(switchFrame);
[UIView commitAnimations];
}
try to disable autolayout from your cell
guys.
I have UITableView with different cells and I have code, which counts height. In one project it works perfect, but in second it returns height equals 0.
What could be causing this?
My code:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
CGFloat cellWidth = 320.0f;
CGSize size = [cell.textLabel.text sizeWithFont:cell.textLabel.font constrainedToSize:CGSizeMake(cellWidth, CGFLOAT_MAX) lineBreakMode:cell.textLabel.lineBreakMode];
CGFloat height = size.height;
NSLog(#"Height: %f", height);
return height;
}
The heightForRowAtIndexPath: delegate method gets called before the cellForRowAtIndexPath: delegate method. In your code you are calculating your cell height based on cell.textLabel.text, but the text in the cell has not been set yet.
You need to get the text to use in this method from somewhere else other than the table cell (presumably you can get it from wherever you are getting it from when you set the textlabel.text value in cellForRowAtIndexPath).
I am trying to create a table with cells that contain UITextViews inside them for variable amounts of text. I need to specify the height of each cell to match the content size of the textviews. I am using...
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITextView *historyNode = [[[UITextView alloc]init]autorelease];
historyNode.text = (#"%#",[globalArrayWithStrings objectAtIndex:[indexPath row]]);
NSLog(#"%2.0f",historyNode.frame.size.height);
return historyNode.contentSize.height;
}
For some reason, it always prints 0. If i print the height of a textview created in interface builder, it prints the correct value. Any ideas how to get around this or why I can't get the size of a textview until it is added to the view.
You can use the sizeWithFont: method to find the dynamic height. Your heightForRowAtIndexPath: should look like this.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *text = [globalArrayWithStrings objectAtIndex:[indexPath row]];
CGSize maxSize = CGSizeMake(textViewWidth, 999); // 999 can be any maxmimum height you want
CGSize newSize = [text sizeWithFont:aFont constrainedToSize:maxSize lineBreakMode:textViewLineBreakMode];
return newSize.height;
}
First set the frame of the uitextView and then you will get the height of the text view.
im making a iphone app so that any text in the detailTextlabel will fit no matter what
for example...if the detailTextLabel is short it will show a normal size cell however it if is long it will show a bigger cell
thank you :D
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGSize maxSize = CGSizeMake(max_size);
CGSize cellSize = [yourString
sizeWithFont:[UIFont systemFontOfSize:15]
constrainedToSize:maxSize
lineBreakMode:UILineBreakModeWordWrap];
return cellSize.height;
}
Consider my implementation:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (theSection) {
case 0:
if (indexPath.row ==6) //the row you need to check for height (if any)
{
if([yourString length]<=35) // if less than 35 chars, just return a basic size of 50
{
return 50;
}
CGSize detailSize = [yourString sizeWithFont:[UIFont systemFontOfSize:20]constrainedToSize:CGSizeMake(270, 4000)lineBreakMode:UILineBreakModeWordWrap];
return detailSize.height;
}
hope it helps, to help your understanding you may want to have a look at 'sizeWithFont' to understand what this is doing to return a size.
You have to use the tableView:heightForRowAtIndexPath: method on your UITableViewDelegate to return the height of the row. Note that this method should be very fast if you have more than a few rows, as it will be called for every row in the table rather than just the visible rows.