I create custom cells within my tableview some have images and are tall some are just text. The height of the cells are calculated in heightForRowAtIndexPath, which I beleive is done before cellForRowAtIndexPath is called. I want to place an imageview at the bottom of the cell regardless of heigh, but I am not sure how to get the calculated height from within cellForRowAtIndexPath?
Too late for an answer..
But, like #user216661 pointed out, the problem with taking the height of the Cell or the ContentView is that it returns the cells original height. Incase of rows with Variable height, this is an issue.
A better solution is to get the Rect of the Cell (rectForRowAtIndexPath) and then get the Height from it.
- (UITableViewCell *)tableView:(UITableView *)iTableView cellForRowAtIndexPath:(NSIndexPath *)iIndexPath {
UITableViewCell *aCell = (UITableViewCell *)[iTableView dequeueReusableCellWithIdentifier:aCellIdentifier];
if (aCell == nil) {
CGFloat aHeight = [iTableView rectForRowAtIndexPath:iIndexPath].size.height;
// Use Height as per your requirement.
}
return aCell;
}
You can ask the delegate, but you'll be asking twice since the tableView already asks and sizes the cell accordingly. It's better to find out from the cell itself...
// in cellForRowAtIndexPath:, deque or create UITableViewCell *cell
// this makes the call to heightForRow... and sizes the cell
CGFloat cellHeight = cell.contentView.bounds.size.height;
// alter the imageView y position (assuming the rest of the frame is correct)
CGRect imageFrame = myImageView.frame;
imageFrame.y = cellHeight - imageFrame.size.height; // place the bottom edge against the cell bottom
myImageView.frame = imageFrame;
You are allowed to call heightForRowAtIndexPath yourself! Just pass the indexPath from cellForRowAtIndexPath as an argument and you can know the height of the cell you are setting up.
Assuming you are using a UITableViewController, just use this inside cellForRowAtIndexPath...
float height = [self heightForRowAtIndexPath:indexPath]
Related
I have a UITableView populated with custom UITableViewCells. Within those custom cells, I have a UITextField and a "See More" UIButton. The purpose of the UIButton is to dynamically expand that particular UITableCell when the user wishes to read more of the text. In the same way, when the user wishes to return to the original size, the user clicks the Button again, and the UITableViewCell will shrink to the original size.
Since the cell isn't being selected, I setup an IBAction within the Custom Cell like such:
//Within CustomCell.m
- (IBAction)showMoreText:(id)sender
{
//instance bool variable to flag whether the cell has been resized
self.hasBeenResized = YES;
//turn off mask to bounds, otherwise cell doesnt seem to resize
[[self.cellView layer] setMasksToBounds:NO];
// Calculate the new sizes and positions for the textView and the button
CGRect newTextViewFrame = self.textView.frame;
newTextViewFrame.size.height = self.textView.contentSize.height;
self.textView.frame = newTextViewFrame;
CGFloat bottomYPos = self.textView.frame.origin.y + self.textView.frame.size.height;
CGRect buttonFrame = self.showMoreButton.frame;
buttonFrame.origin.y = bottomYPos;
self.showMoreButton.frame = buttonFrame;
// Call begin and end updates
[(UITableView*) self.superview beginUpdates];
[(UITableView*) self.superview endUpdates];
// Set mask and put rounded corners on the cell
[[self.cellView layer] setMasksToBounds:YES];
[[self.cellView layer] setCornerRadius:10.0];
}
Following this, I have this in my ViewController class:
// Within ViewController.m
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"heightForRowAtIndexPath");
CustomCell *cell = (CustomCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath];
if([cell hasBeenResized] == NO)
{
return cell.frame.size.height + 20;
}
else
{
return cell.frame.size.height + cell.textView.frame.origin.y + cell.textView.frame.size.height + cell.showMoreButton.frame.size.height + 20;
}
}
What happens now is I can see the custom cell change the size of its textview, however, the table does not update the row height for that particular cell. Checking on the If-else statement there, it appears that hasBeenResized is always false, even though I set it to YES within the IBACtion of the CustomCell.
I have looked at other solutions here, but they all seem to involve didSelectRowAtIndexPath, which I cannot use in this instance (I have another behavior for the cell when it is selected).
Am I doing this completely wrong? Ideally, what I would like to do is to have the "Show More" button animate downwards as the textview is expanded and vice versa when it's collapsed.
Thank you!
Method beginUpdates won't call reloadData for you - you have to do it manually.
For your case it would be best to call:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
And place your showMoreText code in tableView:cellForRowAtIndexPath: method (for selected cell only)
To Update your tableView, you have to reload it.
[tableView reloadData];
I have a method where I have a (rendered) UITableViewCell*. I would like to know the width/frame of the entire cell.
cell.frame doesn't seem to work - it gives me the frame of a random cell. The closest I got is cell.contentView.frame but it doesn't include the area covered by the accessoryView (see http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7). cell.contentView.superview.frame doesn't work either - it gives me a frame at some arbitrary location just like cell.frame does.
cell.contentView.width + cell.accessoryView.width works except in cases where the accessory view is UITableViewCellAccessoryNone
Any ideas how I can get the entire frame/width of the UITableViewCell in all cases?
Try this,
NSLog(#"Cell Width = %f",cell.frame.size.width);
NSLog(#"Cell Height = %f",cell.frame.size.height);
It will show the current cell Width and Height.
This code gives you the frame of the particular cell selected by indexPath:
// Get the cell rect and adjust it to consider scroll offset
CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
cellRect = CGRectMake(cellRect.origin.x - tableView.contentOffset.x, cellRect.origin.y - tableView.contentOffset.y, cellRect.size.width, cellRect.size.height);
cell.frame will always give you the frame of the cell you're messaging.
And even if it did return a random cell, you should still be able to use the width of the frame, because all the cells have the same width.
I think by default, a cell will use 100% of the width of the UITableView so a UITableView's width is equivalent to a cell's width and because you would likely have set your table cell height using:
[UITableView setRowHeight:tableCellHeight];
Where tableCellHeight is a number of your choice and UITableView is the name of your UITableView not the UITableView class itself.
Then you would have both the width and height of a table cell, or did I misinterpret your question?
How does one obtain the UITableViewCell when within the heightForRowAtIndexPath method, i.e. given the indexPath?
(then I could access the content views I have created to add their heights up)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// How to get the UITableViewCell associated with this indexPath?
}
thanks
EDIT: In fact is there really a valid way to do this? When I put some NSLog statements it seems that heightForRowAtIndexPath it called several times before the calls to cellForRowAtIndexPath (which is where I set up the UILabels in the cell)? This kind implies that I may be tried to use a technique that will not work, i.e. I was hoping in heightForRowAtIndexPath to access the already created labels in each cell to get their heights and add them together for the overall cell row height, HOWEVER if they haven't been set up yet (within cellForRowAtIndexPath) then I guess my approach can't really work?
You can use the tableview's delegate instead of the tableView itself.
id cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
Check the answer here
UPDATED
In new iOS versions and using correct auto constraints you don't need to reference the cell anymore to calculate the cell's height.
Check here
https://www.raywenderlich.com/129059/self-sizing-table-view-cells
This question has no sense because in
heightForRowAtIndexPath
no cells are created yet. Here's how tableView works:
TableView asks it's datasource how many sections it will have. -numberOfSectionsInTableView
Asks it's datasource how many rows each section will have (to know how big scroller should be etc.) -numberOfRowsInSection
Asks it's delegate height of each visible row. To know where cells will be located. - heightForRowAtIndexPath
Lastly it asks datasource to give it a cell to display at given index path -cellForRowAtIndexPath
So you can't access to cells from heightForRowAtIndexPath because with a most likely cell is not created yet.
However in case I misunderstood your question go try:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
The obvious answer is to call cellForRowAtIndexPath, but you may have already discovered there are some issues with that. See this question if you haven't already: UITableView flexible/dynamic heightForRowAtIndexPath
For my last project I used a custom subclass of UICell and implemented a method like this. I then called this from table:heightForRowAtIndexPath: (after looking up the content for that row).
+ (CGFloat) heightOfContent: (NSString *)content
{
CGFloat contentHeight =
[content sizeWithFont: DefaultContentLabelFont
constrainedToSize: CGSizeMake( DefaultCellSize.width, DefaultContentLabelHeight * DefaultContentLabelNumberOfLines )
lineBreakMode: UILineBreakModeWordWrap].height;
return contentHeight + DefaultCellPaddingHeight;
}
i would suggest you to calculate the correct height of a row in table:heightForRowAtIndexPath: by using your data structure (Height of text,images,mutliline text etc..).
And change the component height in their -(void) layoutSubviews method.
If you want to access the cells use tags. But as #Jhaliya has said it is better to calculate the height based on the content of the cell. If your regard is with the position of the cell then I would suggest you to use scroll view and implement the cells yourself wherever you want.
For iOS8:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableViewAutomaticDimension
}
OR
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
But for iOS7, the key is calculate the height after autolayout,
func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
cell.setNeedsLayout()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
return height
}
Note:
1) If multiple lines labels, don't forget set the numberOfLines to 0.
2) Don't forget label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
I'd like to know the best strategy for adaptive cell heights. The cells first know how high they will be when they are created (they contain some textboxes and images) in the cellForRowAtIndexPath method.
My idea was to store the cell's height in a NSMutableDictionary with a cell identifiying key.
The problem is that the heightForRowAtIndexPath: method is called before the cells are created and only then.
How do you manage that usually?
If you want to resize your cells (that is - to make heightForRowAtIndexPath method get called) you can use empty beginUpdates - endUpdates block:
[table beginUpdates];
[table endUpdates];
That will force UITableView to update its geometry and cells heights will be updated
If you want to adapt you cell's height, use - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath from the UITableViewDelegate.
Store the cell's height into a NSArray for example and get it thanks to the indexPath parameter.
I do the following now: I calculate the height from the string height directly, I use:
-(CGFloat) estimateCellheightFromText:(NSString*) text andFont:(UIFont*)ffont widthConstraint:(CGFloat)width andLineBreakMode:(UILineBreakMode)lbMode{
CGSize stringSize = [text sizeWithFont:ffont constrainedToSize:CGSizeMake(width, 9999) lineBreakMode:lbMode]; return stringSize.height;}
My app needs to have variable height table cells (as in each table cell differs in height, not that each cell needs to be able to resize itself).
I have a solution that currently works, but it's kludgy and slow.
My Current Solution:
Before the table cells are rendered, I calculate how high each cell needs to be by calling sizing methods such as -sizeWithFont:constrainedToSize: on its data. I then add up the heights, allow for some padding and store the result with the data.
Then when my UITableViewDelegate receives the -tableview:heightForRowAtIndexPath: I work out which item will be rendered for that cell and return the height that I calculated previously.
As I said, this works, but calling -sizeWithFont:constrainedToSize: is very slow when you're doing it for hundreds of items sequentially, and I feel it can be done better.
So for this to work, I had to maintain two parts of code - one that would calculate the cell heights, and one that would actually draw the cells when the time comes.
If anything about the model item changed, I had to update both of these chunks of code, and now and again they still don't even match up perfectly, sometimes resulting in table cells that are slightly too small for a given item, or too large.
My Proposed Solution:
So I want to do away with the precalculating the cell height. A) because it breaks the MVC paradigm and B) because it's slow.
So my cell draws itself, and as a result, ends up with the correct cell height. My problem is that I have no way of telling the table view the height of the cell before its drawn - by which time its too late.
I tried calling -cellForRowAtIndexPath: from within -tableView:heightForRowAtIndexPath: but this gets stuck in an infinite loop, since the first calls the second at some point, and vice versa (at least this is what I saw when I tried it).
So that option is out of the question.
If I don't specify a size in the height for row delegate method, then the table view goes screwwy. The cells are the perfect height, but their x position is that of cells of fixed heights.
Messed Table Cells http://jamsoftonline.com/images/messed_table_cells.png
Notice how the bottom cell is the correct size - it's just overlapping the previous cell, and the previous cell overlaps its previous, and so on and so forth.
Also using this method, while scrolling there is some artifacting occurring which I think may be related to the reuse identifier for the cells.
So any help here would be gratefully appreciated.
Here's what I use. NSString has a method that will tell you the dimensions of a textbox based on the font information and the height/width constraints you give it.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [self getTextForIndexPath:indexPath];
UIFont *font = [UIFont systemFontOfSize:14];
CGSize size = [self getSizeOfText:text withFont:font];
return (size.height + 11); // I put some padding on it.
}
Then you write a method pull the text for this cell...
- (NSString *)getTextForIndexPath:(NSIndexPath *)indexPath
{
NSString *sectionHeader = [self.tableSections objectAtIndex:[indexPath section]];
NSString *sectionContent = [self.tableData objectForKey:sectionHeader];
return sectionContent;
}
And this is to get the size of the text.
- (CGSize)getSizeOfText:(NSString *)text withFont:(UIFont *)font
{
return [text sizeWithFont:font constrainedToSize:CGSizeMake(280, 500)];
}
Just a thought:
What if you had, say, six different types of cells each with their own identifier and a fixed height. One would be for a single-line cell, the other for a two-line cell, etc...
Every time your model changes, calculate the height for that row then find the nearest cell type that has height closest to what you need. Save that celltype identifier with the model. You can also store the fixed row height for that cell in the model so you can return it in the tableview:heightForRowAtIndexPath call (I wouldn't get too hung up on forcing it to calculate inside the cell class itself--technically it's not part of the cell drawing functionality and more something the tableview uses to decide which cell type to create).
At runtime, when asked to return a cell for that row all you need to do is create (or obtain from the cell cache) a cell with the celltype identifier, load the values and you're good to go.
If the cell height calculation is too slow, then you could pull the same trick the tableview cache does and do it only on-demand when the cell comes into view. At any given time, you would only have to do it for the cells in view, and then only for a single cell as it scrolls into view at either end.
I realise this won't work for you due to the infinite loop you mention, but I've had some success with calling the cells layoutSubViews method
Though this may be a little inefficient due to multiple calls to both cellForRowAtIndexPath and layoutSubViews, I find the code is cleaner.
-(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCell *cell = (MyCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
[cell layoutSubviews];
return CGRectGetHeight(cell.frame);
}
And in the layout code:
- (void)layoutSubviews
{
[super layoutSubviews];
//First expand the label to a large height to so sizeToFit isn't constrained
[self.myArbitrarilyLengthLabel setFrame:CGRectMake(self.myArbitrarilyLengthLabel.frame.origin.x,
self.myArbitrarilyLengthLabel.frame.origin.y,
self.myArbitrarilyLengthLabel.frame.size.width,
1000)];
//let sizeToFit do its magic
[self.myArbitrarilyLengthLabel sizeToFit];
//resize the cell to encompass the newly expanded label
[self setFrame:CGRectMake(self.frame.origin.x,
self.frame.origin.y,
self.frame.size.width,
CGRectGetMaxY(self.myArbitrarilyLengthLabel.frame) + 10)];
}