How to ask a UITableViewCell its height? - ios

Following https://stackoverflow.com/a/7313410/1971013 I need to get the height of each of my cells in - (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath.
But when I do:
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
MyTableViewCell* cell = (MyTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
return cell.height;
}
this (of course) results in unwanted recursion blowing up my app.
Any ideas of how I should do this?

The best answer is, sadly, I don't know. That's up to you here. The heightForRowAtIndexPath from UITableViewDelegate is what the UITableView uses to ask you what the height should be. In this function you are determining that.
Perhaps you can save it in your own memory in cellForRowAtIndexPath after you dequeueReusableCellWithIdentifier.
But if your linked questions are any indication, you're trying to expand a selected cell, correct? You can determine if it's the selected cell via
if ([indexPath compare:[tableView indexPathForSelectedRow]] == NSOrderedSame)
return 88; // some expanded value
else
return 44; // the default value

Gutblender is right: in the usual case, you don't ask a cell its height, you tell it.
It's important to embrace the fact that cells are recycled. From a practical perspective, you can't examine cells that are off screen because they don't exist.
Cautions aside, here are the options:
Create a method that defines the height of your table view cells, and use that method to calculate the value you return from heightForRowAtIndexPath. You can then call that method from elsewhere to determine what the height of the cell will be at a given index path, whether or not it exists. This is the standard approach. It's also, 99.9% of the time, the correct one.
Iterate through the table view's subviews. A bad idea because you don't control the NSTableView class, and the view hierarchy can change. And, again: cells are recycled.
Create custom table view cell class, use something like the MVVM design pattern and let your model objects vend the table view cells. You won't have to worry about the view hierarchy, but again: cells are recycled – you will end up managing stale or incorrect state.

One method I've used on static table view's that don't have any reuse and the cells are pre-generated, is create a subclassed tableView cell with a height property, so in heightForRowAtIndexPath you can get the cell for that indexPath and return the cell's height you hard-coded in that you wanted for the cell. That way, any time you want to change the height you can change it directly on the cell itself and when you reloadData it should update the cell height.

Related

Cell content not showing up if heightForRowAtIndexPath is implemented

I'm using iOS 9.2 and XCode 7.2.
I have a basic UITableView object in which i add different kind of UITableViewCell subclasses.
With one of them, when i set manually the height overriding the method heightForRowAtIndexPath, i don't get any content on the cell.
If i return -1 as height(the default value for the UITable row height), i get my cell showing up correctly. The thing is that i do need a different height for this row because the content is quite big.
here is the code for heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
MenuItem *menuItem = [menuManager menuItemAtIndex:indexPath.row];
if ([menuItem type] == MenuItemTypeEditorHeader) {
return 100;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
MenuItem is a class containing the specific menu object' informations, such as the type. The result is that the cell is showed up at the correct height, but it's empty.
Its not advisable to use heightForRowAtIndexPath anymore - thats old-school. Instead, do this :
Set up autolayout constraints in your cell (if you dont know how to - you need to, its not something you can avoid anymore!)
Create an estimatedRowHeight for autolayout to use, on the tableView. You can set it in the nib/storyboard or programmatically, in viewDidLoad for eg, like this :
self.tableview.estimatedRowHeight = 68.0;
Set your tableview to use 'automatic dimension', like this :
self.tableview.rowHeight = UITableViewAutomaticDimension;
And thats it. If you do these things, then your cells will vary in height according to their constraints. So if one of your subclasses has a height of 150px due to its constraints, that will work perfectly next to another subclass that has a height of 50px. You can also vary the height of a cell dynamically depending on the contents of the cell, for eg when you have labels that expand using 'greater than or equal to' constraints. Also - simply omit the 'heightForRowAtIndexPath' method, you dont need to implement it at all.
Are you calling tableView.reloadData() ?
print the length of menu objects before you call tableView.reloadData().
HeightForRowAtIndexPath just returns height of a row. So may be problem in cellForRowAtIndexPath.

how to develop a custom UICollectionViewLayout that has staggered columns with self-sizing cells?

I'm working on the iOS version of an app I already developed on Android. This app has the following 2 column grid of self-sizing (fixed width but variable height) cells:
Achieving this in the Android version was easy because Google provides a StaggeredGridLayoutManager for its RecyclerView. You specify the number of columns and the direction of the scroll and you are done.
The default UICollectionView layout UICollectionViewFlowLayout doesn't allow the staggered layout I'm looking for, so I have to implement a custom layout. I have watched 2 WWDC videos that talk about this topic (What's New in Table and Collection Views and Advanced User Interfaces with Collection Views) and I more or less have an idea of how it should be implemented.
Step 1. First an approximation of the layout is computed.
Step 2. Then the cells are created and sized with autolayout.
Step 3. Then the controller notifies the of the cell sizes so the layout is updated.
My doubts come when trying to code these steps. I found a tutorial that explains the creation of a custom layout with staggered columns, but it doesn't use autolayout to obtain the size of the cells. Which leaves me with the following questions:
In step 2, how and when can I obtain the cell size?
In step 3, how and when can I notify the layout of the changes?
I want to point out that, as you have mentioned, RayWenderlich PinInterest Layout is exactly the tutorial that'll help you achieve this layout.
To answer your questions - with regards to the tutorial:
In step 2, how and when can I obtain the cell size?
To get the cell height, a delegate method was implemented that was called in the prepareLayout method of the custom UICollectionViewLayout. This method is called once (or twice, I just attempted to run it with a print statement, and I got two calls). The point of prepareLayout is to initialize the cell's frame property, in other words, provide the exact size of each cell. We know that the width is constant, and only the height is changing, so in this line of prepareLayout:
let cellHeight = delegate.collectionView(collectionView!,
heightForItemAtIndexPath: indexPath, withWidth: width)
We obtain the height of the cell from the delegate method that was implemented in the UICollectionViewController. This happens for all the cells we want to show in the collectionView. After obtaining and modifying the height for each cell, we cache the result so we can inspect it later.
Afterwards, for the collectionView to obtain the size of each cell on screen, all it needs to do is query the cache for the information. This is done in layoutAttributesForElementsInRect method of your custom UICollectionViewLayout class.
This method is called automatically by the UICollectionViewController. When the UICollectionViewController needs layout information for cells that are coming onto the screen (as a result of scrolling, for instance, or upon first load), you return the attributes from the cache that you've populated in prepareLayout.
In conclusion to your question: In step 2, how and when can I obtain the cell size?
Answer: Each cell size is obtained within the prepareLayout method of your custom UICollectionViewFlowLayout, and is calculated early in the life cycle of your UICollectionView.
In step 3, how and when can I notify the layout of the changes?
Note that the tutorial does not account for new cells to be added at runtime:
Note: As prepareLayout() is called whenever the collection view’s layout is invalidated, there are many situations in a typical implementation where you might need to recalculate attributes here. For example, the bounds of the UICollectionView might change – such as when the orientation changes – or items may be added or removed from the collection. These cases are out of scope for this tutorial, but it’s important to be aware of them in a non-trivial implementation.
Like he wrote, it's a non trivial implementation that you might need. There is, however, a trivial (very inefficient) implementation that you might adopt if your data set is small (or for testing purposes). When you need to invalidate the layout because of screen rotation or adding/removing cells, you can purge the cache in the custom UICollectionViewFlowLayout to force prepareLayout to reinitialize the layout attributes.
For instance, when you have to call reloadData on the collectionView, also make a call to your custom layout class, to delete the cache:
cache.removeAll()
I realise this is not a complete answer, but some pointers regarding your steps 2 and 3 may be found in the subclassing notes for UICollectionViewLayout.
I presume you have subclassed UICollectionViewFlowLayout since off the top of my head I believe this is a good starting point for making adjustments to the layout to get the staggered appearance you want.
For step 2 layoutAttributesForElementsInRect(_:) should provide the layout attributes for the self sized cells.
For step 3 your layout will have shouldInvalidateLayoutForPreferredLayoutAttributes(_:withOriginalAttributes:) called with the changed cell sizes.
In step 2, how and when can I obtain the cell size?
You need to calculate height of each cell in prepareLayout() method. Result of calculation for each cell should be assigned to UICollectionViewLayoutAttributes variable, and than put it into collection NSDictionary, where key would be NSIndexPath(of each cell), and value would be UICollectionViewLayoutAttributes variable.
Example:
- (void)prepareLayout {
[_layoutMap removeAllObjects];
_totalItemsInSection = [self.collectionView numberOfItemsInSection:0];
_columnsYoffset = [self initialDataForColumnsOffsetY];
if (_totalItemsInSection > 0 && self.totalColumns > 0) {
[self calculateItemsSize];
NSInteger itemIndex = 0;
CGFloat contentSizeHeight = 0;
while (itemIndex < _totalItemsInSection) {
NSIndexPath *targetIndexPath = [NSIndexPath indexPathForItem:itemIndex inSection:0];
NSInteger columnIndex = [self columnIndexForItemAtIndexPath:targetIndexPath];
// you need to implement this method and perform your calculations
CGRect attributeRect = [self calculateItemFrameAtIndexPath:targetIndexPath];
UICollectionViewLayoutAttributes *targetLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:targetIndexPath];
targetLayoutAttributes.frame = attributeRect;
contentSizeHeight = MAX(CGRectGetMaxY(attributeRect), contentSizeHeight);
_columnsYoffset[columnIndex] = #(CGRectGetMaxY(attributeRect) + self.interItemsSpacing);
_layoutMap[targetIndexPath] = targetLayoutAttributes;
itemIndex += 1;
}
_contentSize = CGSizeMake(self.collectionView.bounds.size.width - self.contentInsets.left - self.contentInsets.right,
contentSizeHeight);
}
}
Don't forget to implement following methods:
- (NSArray <UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributesArray = [NSMutableArray new];
for (UICollectionViewLayoutAttributes *layoutAttributes in _layoutMap.allValues) {
if (CGRectIntersectsRect(layoutAttributes.frame, rect)) {
[layoutAttributesArray addObject:layoutAttributes];
}
}
return layoutAttributesArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return _layoutMap[indexPath];
}
These methods would be triggered once you call reloadData() mehtod or invalidateLayout().
In step 3, how and when can I notify the layout of the changes?
Just call self.collectionView.collectionViewLayout.invalidateLayout() and prepareLayout() method would be called once again, so you can recalculate all parameters you need.
You can find my full tutorial about custom UICollectionViewLayout here: https://octodev.net/custom-collectionviewlayout/
Tutorial contains implementation in both languages: Swift and Objective-C.
Would be more than glad to answer all your questions.
The "cell size" is defined by UICollectionViewLayoutAttribute in the layout subclass which mean you can modify it every time you have the chance to touch them. You can set every attributes' size to what you desire.
For example you can do it in layoutAttributesOfElementsInRect(:) , calculate the right size and config all attributes before pass them to collectionView. You can also do it in layoutAttributeOfItemAtIndexPath(:) ,make the calculation when every attribute is created.
Furthermore, consider to provide the desired size by a datasource so every attribute can easily get their size with their index.
For if you want to have the cell size to layout the subviews in a cell, do it in the collectionView delegate method: collectionView:ItemAtIndexPath:
Hope this help.

Calling dequeueReusableCellWithIdentifier:cellId inside heightForRowAtIndexPath

I am working on an ios application,
I have a normal table view. When calling heightForRowAtIndexPath I am doing the folowing
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellId = [self getCellIds][indexPath.row];
BaseTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
return [cell calculateHeigh];
}
Basically I am dequeueing the cell because I have a function calculateHeigh inside every cell that will do the height calculation. this is working fine as intended however I have a concern:
Is it safe to call dequeueReusableCellWithIdentifier: inside the heightForRowAtIndexPath ? will it cause any issue?
EDIT:
Just to clarify why I did this, I have a big amount of custom cells with different identifier that needs to be loaded. and to avoid having a huge if-else statement in my heightForRowAtIndexPath I placed the getter of the cell height in the custom cell that way I just ask it to return it (no calculation is made there), I can't do it as a class method as I don't know which class, I can get the object from the identifier and not the class. And I want to avoid a big if-else just for code readability.
So my concern was with the dequeueReusableCellWithIdentifier: is it heavy to call it when getting the height? will it cause memory issues or lags? or is it worth to just do a bug if-else of use a dictionary?
First of all you should avoid any calculations in table drawing methods(such as heightForRow, cellForRow, etc). These methods are called a lot and although your table may be short and/or not complicated(with custom cells with a lot of labels, buttons and images) you should always try to optimize this drawing process or otherwise user will experience some nasty lag when scrolling.
So you should call some method to prepare data before calling 'reloadTable'
-(void)prepareMethod
{
//get only one cell to calculate all row heights
BaseTableViewCell *cell = [_myTableView dequeueReusableCellWithIdentifier:cellId];
for (NSDictionary* dataObj in _dataArray)
{
//loop through all rows data and set new property for row height
dataObj[#"rowHeight"] = [cell calculateHeigh];
}
}
And then when calling heightForRow just pass this value without any expensive operation(such as probably string calculations):
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//always make sure you don't access unexisting array index
return ( indexPath.row < _dataArray.count ) ? _dataArray[indexPath.row][indexPath.row][#"rowHeight"] : 1.0;
}
Of course you don't need separate method just to populate row heights in your data array - you can populate this value when populating(formatting) your data array to avoid second array iteration. It all depends on your current implementation.
Just remember that expensive drawing methods(not only for table though) should always be as short as possible and just get data needed for drawing and draw. It's really so simple. If you need to make some complicated calculations do it before that(maybe in view init) so your data is prepared before actual drawing. This way your application will be working smoothly even with bigger tables(because no matter how big the table is, UITableViewController draws only visible cells).
Regards,
hris.to
I don't like to have big if statements in heightForRowAtIndexPath and accessing a cell using dequeueReusableCellWithIdentifier. Your approach getting cell height from each cell quite is reasonable. I believe your calculateHeigh return value depends on the table data you pass into the cell.
In BaseTableViewCell.h
+ (CGFloat)heightWithData:(id)data;
In BaseTableViewCell.m
+ (CGFloat)heightWithData:(id)data
{
//put your calculateHeigh logic here. I believe your calculateHeigh depends on the data each cell has.
}
Then you can do
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [BaseTableViewCell heightWithData:[self.tableData objectAtIndex:indexPath.row]];
}
If you do this, you don't need to access each cell object to get cell height.
You should not use this method to provide the calculation. Based on what I can see on your setup, you are calculating the height based on the values already on your cell. What happens is that the cell dequeue system will give you a cell to reuse, but because it's sharing cells from multiple index paths, that cell probably has data that belongs to a record of an index path different from the current one. Get the calculate height code and try to reproduce it inside the datasource callback you are using.

setNeedsLayout relayouts the cell only after is has been displayed

I have a table view with cells, which sometimes have an optional UI element, and sometimes it has to be removed.
Depending on the element, label is resized.
When cell is initialised, it is narrower than it will be later on. When I set data into the label, this code is called from cellForRowAtIndexPath:
if (someFlag) {
// This causes layout to be invalidated
[flagIcon removeFromSuperview];
[cell setNeedsLayout];
}
After that, cell is returned to the table view, and it is displayed. However, the text label at that point has adjusted its width, but not height. Height gets adjusted after a second or so, and the jerk is clearly visible when all cells are already displayed.
Important note, this is only during initial creation of the first few cells. Once they are reused, all is fine, as optional view is removed and label is already sized correctly form previous usages.
Why isn't cell re-layouted fully after setNeedsLayout but before it has been displayed? Shouldn't UIKit check invalid layouts before display?
If I do
if (someFlag) {
[flagIcon removeFromSuperview];
[cell layoutIfNeeded];
}
all gets adjusted at once, but it seems like an incorrect way to write code, I feel I am missing something else.
Some more code on how cell is created:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ProfileCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
[cell setData:model.items[indexPath.row] forMyself:YES];
return cell;
}
// And in ProfileCell:
- (void)setData:(Entity *)data forMyself:(BOOL)forMe
{
self.entity = data;
[self.problematicLabel setText:data.attributedBody];
// Set data in other subviews as well
if (forMe) {
// This causes layouts to be invalidated, and problematicLabel should resize
[self.reportButton removeFromSuperview];
[self layoutIfNeeded];
}
}
Also, if it matters, in storyboard cell looks like this, with optional constraint taking over once flag icon is removed:
I agree that calling layoutIfNeeded seems wrong, even though it works in your case. But I doubt that you're missing something. Although I haven't done any research on the manner, in my experience using Auto Layout in table cells that undergo a dynamic layout is a bit buggy. That is, I see herky jerky layouts when removing or adding subviews to cells at runtime.
If you're looking for an alternative strategy (using Auto Layout), you could subclass UITableViewCell and override layoutSubviews. The custom table cell could expose a flag in its public API that could be set in the implementation of tableView:cellForRowAtIndexPath:. The cell's layoutSubviews method would use the flag to determine whether or not it should include the optional UI element. I make no guarantees that this will eliminate the problem however.
A second strategy is to design two separate cell types and swap between the two in tableView:cellForRowAtIndexPath: as necessary.
You've added additional code to the question, so I have another suggestion. In the cell's setData:forMyself: method, try calling setNeedsUpdateConstraints instead of layoutIfNeeded.

Is there a way to update the height of a single UITableViewCell, without recalculating the height for every cell?

I have a UITableView with a few different sections. One section contains cells that will resize as a user types text into a UITextView. Another section contains cells that render HTML content, for which calculating the height is relatively expensive.
Right now when the user types into the UITextView, in order to get the table view to update the height of the cell, I call
[self.tableView beginUpdates];
[self.tableView endUpdates];
However, this causes the table to recalculate the height of every cell in the table, when I really only need to update the single cell that was typed into. Not only that, but instead of recalculating the estimated height using tableView:estimatedHeightForRowAtIndexPath:, it calls tableView:heightForRowAtIndexPath: for every cell, even those not being displayed.
Is there any way to ask the table view to update just the height of a single cell, without doing all of this unnecessary work?
Update
I'm still looking for a solution to this. As suggested, I've tried using reloadRowsAtIndexPaths:, but it doesn't look like this will work. Calling reloadRowsAtIndexPaths: with even a single row will still cause heightForRowAtIndexPath: to be called for every row, even though cellForRowAtIndexPath: will only be called for the row you requested. In fact, it looks like any time a row is inserted, deleted, or reloaded, heightForRowAtIndexPath: is called for every row in the table cell.
I've also tried putting code in willDisplayCell:forRowAtIndexPath: to calculate the height just before a cell is going to appear. In order for this to work, I would need to force the table view to re-request the height for the row after I do the calculation. Unfortunately, calling [self.tableView beginUpdates]; [self.tableView endUpdates]; from willDisplayCell:forRowAtIndexPath: causes an index out of bounds exception deep in UITableView's internal code. I guess they don't expect us to do this.
I can't help but feel like it's a bug in the SDK that in response to [self.tableView endUpdates] it doesn't call estimatedHeightForRowAtIndexPath: for cells that aren't visible, but I'm still trying to find some kind of workaround. Any help is appreciated.
As noted, reloadRowsAtIndexPaths:withRowAnimation: will only cause the table view to ask its UITableViewDataSource for a new cell view but won't ask the UITableViewDelegate for an updated cell height.
Unfortunately the height will only be refreshed by calling:
[tableView beginUpdates];
[tableView endUpdates];
Even without any change between the two calls.
If your algorithm to calculate heights is too time consuming maybe you should cache those values.
Something like:
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat height = [self cachedHeightForIndexPath:indexPath];
// Not cached ?
if (height < 0)
{
height = [self heightForIndexPath:indexPath];
[self setCachedHeight:height
forIndexPath:indexPath];
}
return height;
}
And making sure to reset those heights to -1 when the contents change or at init time.
Edit:
Also if you want to delay height calculation as much as possible (until they are scrolled to) you should try implementing this (iOS 7+ only):
#property (nonatomic) CGFloat estimatedRowHeight
Providing a nonnegative estimate of the height of rows can improve the
performance of loading the table view. If the table contains variable
height rows, it might be expensive to calculate all their heights when
the table loads. Using estimation allows you to defer some of the cost
of geometry calculation from load time to scrolling time.
The default value is 0, which means there is no estimate.
This bug has been fixed in iOS 7.1.
In iOS 7.0, there doesn't seem to be any way around this problem. Calling [self.tableView endUpdates] causes heightForRowAtIndexPath: to be called for every cell in the table.
However, in iOS 7.1, calling [self.tableView endUpdates] causes heightForRowAtIndexPath: to be called for visible cells, and estimatedHeightForRowAtIndexPath: to be called for non-visible cells.
Variable row heights have a very negative impact on your table view performance. You are talking about web content that is displayed in some of the cells. If we are not talking about thousands of rows, thinking about implementing your solution with a UIWebView instead of a UITableView might be worth considering. We had a similar situation and went with a UIWebView with custom generated HTML markup and it worked beautifully. As you probably know, you have a nasty asynchronous problem when you have a dynamic cell with web content:
After setting the content of the cell you have to
wait until the web view in the cell is done rendering the web content,
then you have to go into the UIWebView and - using JavaScript - ask the HTML document how high it is
and THEN update the height of the UITableViewCell.
No fun at all and lots of jumping and jittering for the user.
If you do have to go with a UITableView, definitely cache the calculated row heights. That way it will be cheap to return them in heightForRowAtIndexPath:. Instead of telling the UITableView what to do, just make your data source fast.
Is there a way?
The answer is no.
You can only use heightForRowAtIndexPath for this.
So all you can do is make this as inexpensive as possible by for example keeping an NSmutableArray of your cell heights in your data model.
I had a similar issue(jumping scroll of the tableview on any change) because I had
(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 500; }
commenting the entire function helped.
Use the following UITableView method:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
You have to specify an NSArray of NSIndexPath which you want to reload. If you want to reload only one cell, then you can supply an NSArray that holds only one NSIndexPath.
NSIndexPath* rowTobeReloaded = [NSIndexPath indexPathForRow:1 inSection:0];
NSArray* rowsTobeReloaded = [NSArray arrayWithObjects:rowTobeReloaded, nil];
[UITableView reloadRowsAtIndexPaths:rowsTobeReloaded withRowAnimation:UITableViewRowAnimationNone];
The method heightForRowAtIndexPath: will always be called but here's a workaround that I would suggest.
Whenever the user is typing in the UITextView, save in a local variable the indexPath of the cell. Then, when heightForRowAtIndexPath: is called, verify the value of the saved indexPath. If the saved indexPath isn't nil, retrieve the cell that should be resized and do so. As for the other cells, use your cached values. If the saved indexPath is nil, execute your regular lines of code which in your case are demanding.
Here's how I would recommend doing it:
Use the property tag of UITextView to keep track of which row needs to be resized.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
[textView setDelegate:self];
[textView setTag:indexPath.row];
...
}
Then, in your UITextView delegate's method textViewDidChange:, retrieve the indexPath and store it. savedIndexPath is a local variable.
- (void)textViewDidChange:(UITextView *)textView
{
savedIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}
Finally, check the value of savedIndexPath and execute what it's needed.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (savedIndexPath != nil) {
if (savedIndexPath == indexPath.row) {
savedIndexPath = nil;
// return the new height
}
else {
// return cached value
}
}
else {
// your normal calculating methods...
}
}
I hope this helps! Good luck.
I ended up figuring out a way to work around the problem. I was able to pre-calculate the height of the HTML content I need to render, and include the height along with the content in the database. That way, although I'm still forced to provide the height for all cells when I update the height of any cell, I don't have to do any expensive HTML rendering so it's pretty snappy.
Unfortunately, this solution only works if you've got all your HTML content up-front.

Resources