I have a UITableViewCell with dynamic height based on textual content. In tableview:heightForRowAtIndexPath: the height gets calculated. This works well.
When the cell enters editing mode with editingStyle UITableViewCellEditingStyleDelete it indents the cell a little and can push the content out some causing the rowHeight to change. Again this works well as switching the tableview to editing causes the table to relayout and therefor the rowHeight gets recalculated.
When you click the red button or swipe the cell a Delete button appears on right hand side of the cell. This however does NOT trigger a relayout of the table, only the cell itself. The problem here is that if the content goes over the bottom edge the cell does not get resized.
Is there a way to trigger a re-layout of the table when the delete button appears?
In general, height of UITableViewCell can be only setup via tableview:heightForRowAtIndexPath:, and the latter method only be called once (per row per section) before tableView:cellForRowAtIndexPath:, so you have to [tableview reloadData] if want to adjust the UITableViewCell height.
Maybe your can do something in these delegate methods
// The willBegin/didEnd methods are called whenever the 'editing' property is automatically changed by the table (allowing insert/delete/move). This is done by a swipe activating a single row
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath;
To your question, you could reset the properties of the text control at - (void)layoutSubviews in your custom UITableViewCell.
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat theWidth = self.frame.size.height; //changed when entering the edit style
...
}
Hope these will help you.
Related
I have a UITableView inside a UITableViewCell pinned on all four sides. I want to change the height of UITableView based on number of cells it has. For that I have subclassed UITableViewCell (name- TableCell). Inside it, I am using this code to update the tableView height.:
-(void)layoutSubviews{
[super layoutSubviews];
if ([self.data count] <= 5) {
self.tableHeightConstraint.constant = ([self.data count]) * 44.0f;
} else {
self.tableHeightConstraint.constant = 5 * 44.0f;
}
}
I change the height constraint of TableView above dynamically. It works sometimes but other times leads to breaking constraints leading to jittery behaviour of app.
Is this the correct way of changing height?
Please help!
Edit 1:
I am inserting and deleting cells at runtime. The inserted cell has a tableview whose size is dynamic based on number of cells. When I insert (while including the above code) the insertion is correct. But when I delete, the cell (having uitableview) is still visible. When I scroll the cell out of screen, then only it goes invisible.
Kindly help!
if you have tableview inside tableViewCell that is pinned to all sides of the cell (bottom,left,right,top) and it doesn't have a high content hugging priority you can set the height of the cell in the outer tableView in:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
the inner tableview will be stretched since it have pinned constraints
In my cell.xib, I have a label, with constraints to all its sides. I've set that label to lines = 0 and line-break = word wrap. Then, I do this to my TableView:
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 100.0
Everything works great, and my UITableViewCell is auto-height. If the text is long, then my tableView intelligently calculates the size.
The problem is -- how do I tell my UITableView to "re-calculate" the size once the content changes in the cell?
My cell could call its delegate, and in this delegate, I'd like the TableView to re-draw the height.
Right now, the content in my cells change constantly, but the cell height never changes.
There is a documented way to do this. See UITableView.beginUpdates() documentation:
You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell.
So, the correct solution is:
tableView.beginUpdates()
tableView.endUpdates()
Also note that there is a feature that is not documented - you can add a completion handler for the update animation here, too:
tableView.beginUpdates()
CATransaction.setCompletionBlock {
// this will be called when the update animation ends
}
tableView.endUpdates()
However, tread lightly, it's not documented (but it works because UITableView uses a CATransaction for the animation).
I've found the best way to get it to check heights is to call, after whatever text change has been made, in order:
self.tableView.beginUpdates()
self.tableView.endUpdates()
This causes the tableView to check heights for all visible cells and it will cause changes to be made as needed.
I think the simplest solution is to reload that specific cell. For example:
- (void)yourDelegateMethodOfCell:(UITableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//If cell is not visible then indexPath will be nil so,
if (indexPath) {
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
You can get automatic cell height by this code
tableView.beginUpdates()
// add label text update code here
// label.numberOfLines = label.numberOfLines == 0 ? 1 : 0
tableView.endUpdates()
Below is the reference to this solution with demo :
GitHub-RayFix-MultiLineDemo
I got your point, and I met the problem before.
Your cell is in AutoLayout, and you wish the cell changes by itself. Here is recommended answer for that: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights , so we don't talk about that again.
So here we focus on your problem.
Since the content of your cell changes constantly, which means the content has updated. Here we suppose the content is a label. We set the label's text, and surely the label's height maybe change.
Here comes the point: How does the label's change inform the cell to update?
We use AutoLayout, surely we have to update the constraint of height for the label.
And I think it will work!
Below is the detail step:
1. We setup the constraints for the cell's subviews.(I think it's done)
2. One of the label's height is changed by itself.(I think it's done too)
3. We get the new height of the label, and update the constraint of height for the label.(what we have to do)
Seems you wanted to reload the particular cell/cells based on content changes
Here we have a couple of options
1) Need to reload the entire table view .
or else
2) Reload particular cell/cells based on content changes.
But the preferred option would be reloading the particular cell,
Why because
when you asked your UITableView instance to reload a couple of cells,tableview will asks its datasource(-tableView:cellForRowAtIndexPath:) to get the updated content,so that the reloaded cells will have the updated size & Updated content aswell.
Try to reload the cells when the content/height need to update based on content
Hope that helps!
Happy coding :)
Take a look at this answer : Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
How to achieve dynamic cell size is described very thorough there.
As a suggestion for testing try adding setNeedsLayout to:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
or
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
To disable the annoying tableView animation:
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
// cell.titleLabel?.text = "title"
// cell.detailTextLabel?.text = "Very long text ..."
// cell.detailTextLabel?.numberOfLines = 0
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
You can resize your cell height by implementing below method only
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
To establish some context :
I have a UITableView with n cells.
When the user selects a cell they the cell expands and the user experience then continues within that cell.
There are some animations that take place within the cell.
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath
{
//expand cell/increase cell height animated
//add a button to bottom of cell with target(didpressbutton:)
}
- (void)didpressbutton:(id)sender
{
//perform complex animating rearranging UI elements
}
At the end of the flow the user needs to comeback to the original tableview.
But the cell with the misaligned UI elements are still showing as it is dequeuing the old cells.
Is there any way for me to clear the cached cell or reinitialise them?
But the cell with the misaligned UI elements are still showing as it is dequeuing the old cells.
Is there any way for me to clear the cached cell or reinitialise them?
You are obviously misusing -prepareForReuse:. Implement this method to reset any state the cells have.
In my custom UITableViewCell I set the height of row as
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 70;
}
As a result (I believe), when data is rendered, the last cell is not rendered correctly.
This happens on all views where I set the heightForRowAtIndexPath. How do I fix it, so that I can see last cell as well?
You are returning the height of the cell correctly and this issue seems to be related with table view and not with the cell, I think you need to reduce the height of the tableView.
If you are using AutoLayout then you need to set constraints to your tableView with Respect to its superview.
Else if just set
[tableView setAutoresizingMask:UIViewAutoresizingMaskFelxibleHeight | UIViewAutoResizingMaskFlexibleWidth];
Perhaps your UITableView's height is not right, check it.
Consider an standard, vertically scrolling flow layout populated with enough cells to cause scrolling. When scrolled to the bottom, if you delete an item such that the content size of the collection view must shrink to accommodate the new number of items (i.e. delete the last item on the bottom row), the row of cells that scroll in from the top are hidden. At the end of the deletion animation, the top row appears without animation - it's a very unpleasant effect.
In slow motion:
It's really simple to reproduce:
Create a new single view project and change the default ViewController to be a subclass of UICollectionViewController
Add a UICollectionViewController to the storyboard that uses a standard flow layout, and change its class to ViewController. Give the cell prototype the identifier "Cell" and a size of 200x200.
Add the following code to ViewController.m:
#interface ViewController ()
#property(nonatomic, assign) NSInteger numberOfItems;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.numberOfItems = 19;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.numberOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.numberOfItems--;
[collectionView deleteItemsAtIndexPaths:#[indexPath]];
}
#end
Additional Info
I've seen other manifestations of this problem when dealing with collection views, it's just that the above example seems the simplest to demonstrate the issue. UICollectionView seems to go into some kind of paralysed state of panic during the default animations, and refuses to unhide certain cells until after the animation completes. It even prevents manual calls to cell.hidden = NO on hidden cells from having an effect (hidden is still YES afterwards). Dropping down to the underlying layer and setting hidden there works, provided you can get a reference to the cell you want to unhide, which is non-trivial when dealing with cells that haven't been displayed yet.
-initialLayoutAttributesForAppearingItemAtIndexPath is being called for every item visible at the time of the call to deleteItemsAtIndexPaths:, but not for the ones that are scrolled into view. It is possible work around the issue by calling reloadData inside a batch update block immediately afterwards, which appears to make the collection view realise that the top row is about to appear:
[collectionView deleteItemsAtIndexPaths:#[indexPath]];
[collectionView performBatchUpdates:^{
[collectionView reloadData];
} completion:nil];
But unfortunately this is not an option for me. I am trying to implement some custom animation timing by manipulating the cell layers & animations, and calling reloadData really throws things out of whack by causing unnecessary layout callbacks.
Update: A bit of investigation
I added log statements to a lot of layout methods and looked through some stack frames to try and find out what's going wrong. Crucially, I'm checking when layoutSubviews is called, when the collection view asks for layout attributes from the layout object (layoutAttributesForElementsInRect:) and when applyLayoutAttributes: is called on the cells.
I would expect to see a sequence of methods like this:
// user taps cell (to delete it)
-deleteItemsAtIndexPaths:
-layoutAttributesForElementsInRect:
-finalLayoutAttributes...: // Called for the item being deleted
-finalLayoutAttributes...: // \__ Called for each index path visible
-initialLayoutAttributes...: // / when deletion started
-applyLayoutAttributes: // Called for the item being deleted, to apply final layout attributes
// collection view begins scrolling up
-layoutSubviews: // Called multiple times as the
-layoutAttributesForElementsInRect: // collection view scrolls
// ... for any new set of
// ... attributes returned:
-collectionView:cellForItemAtIndexPath:
-applyLayoutAttributes: // Sets the standard attributes for the new cell
// collection view finishes scrolling
Most of this is happening; layout is correctly triggered as the view scrolls, and the collection view properly queries the layout for the attributes of cells to be displayed. However, collectionView:cellForItemAtIndexPath: and the corresponding applyLayoutAttributes: methods are not being called until after the deletion, when layout is invoked one last time causing the hidden cells to be assigned their layout attributes (sets hidden = NO).
So it seems that despite receiving all the correct responses from the layout object, the collection view has some kind of flag set to not update the cells during the update. There is a private method on UICollectionView called from within layoutSubviews that seems responsible for refreshing the cells' appearance: _updateVisibleCellsNow:. This is from where the data source eventually gets asked for a new cell before applying the cells starting attributes, and it seems this is the point of failure, as it is not being called when it should be.
Additionally, this does seem to be related to the update animation, or at least cells are not updated for the duration of the insertion/deletion. For example the following works without glitches:
- (void)addCell
{
NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForItem:self.numberOfItems
inSection:0];
self.numberOfItems++;
[self.collectionView insertItemsAtIndexPaths:#[indexPathToInsert]];
[self.collectionView scrollToItemAtIndexPath:indexPathToInsert
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:YES];
}
If the above method is called to insert a cell while the inserted cell is outside the current visible bounds, the item is inserted without animation and the collection view scrolls to it, properly dequeuing and displaying cells on the way.
Problem occurs in iOS 7 & iOS 8 beta 5.
Adjust your content insets so that they go beyond the bounds of the device's screen size slightly.
collectionView.contentInsets = UIEdgeInsetsMake(-5,0,0,0); //Adjust this value until it looks ok