UITableView animation glitch when deleting and inserting cells with varying heights - uitableview

I am having a problem with the animation that UITableView provides when deleting and inserting a cell at the same time.
I have a list of cells lets call them questions. When one question is tapped it should add a cell beneath itself to display the answer to that question. If another answer is already being displayed that answer should be removed from the table.
The issue arises when the cell being inserted is very tall. If it is so tall that it's eventual bounds encroach into the space that the cell to be deleted takes up then during the animation we see the through the answer cell that we are deleting to see the cell that is being added
(see link to video of problem)
the is what my code looks like to move around the cells in the table view
[tableView beginUpdates];
if (deleteIndex) {
[tableView deleteRowsAtIndexPaths:#[deleteIndex] withRowAnimation:UITableViewRowAnimationTop];
}
if (addIndex) {
[tableView insertRowsAtIndexPaths:#[addIndex] withRowAnimation:UITableViewRowAnimationTop];
}
[tableView endUpdates];
I have tried
[tableView beginUpdates];
if (deleteIndex) {
[tableView deleteRowsAtIndexPaths:#[deleteIndex] withRowAnimation:UITableViewRowAnimationTop];
}
[tableView endUpdates];
//do stuff to update data source
[tableView beginUpdates];
if (addIndex) {
[tableView insertRowsAtIndexPaths:#[addIndex] withRowAnimation:UITableViewRowAnimationTop];
}
[tableView endUpdates];
But because there's no callback confirming that the table view did complete the first set update before starting the second block pretty much the same problem occurs. I know I could use perform selector with delay, but this seems like a bandaid.
Second I tried to encompass the animation in a block like this
[UIView animateWithDuration:0.0 animations:^{
[tableView beginUpdates];
if (deleteIndex) {
[tableView deleteRowsAtIndexPaths:#[deleteIndex] withRowAnimation:UITableViewRowAnimationTop];
}
[tableView endUpdates];
//do stuff to update data source
} completion:^(BOOL finished) {
[tableView beginUpdates];
if (addIndex) {
[tableView insertRowsAtIndexPaths:#[addIndex] withRowAnimation:UITableViewRowAnimationTop];
}
[tableView endUpdates];
}];
Again, because the completion block is fired after we call endUpdates not after the updates actually complete this does not resolve the use.
I also went into storyboard to be sure that clip subviews to bounds is selected for the cells. Again, this does not resolve the issue because we are not seeing a subview of the cell expand beyond it's expected height.
Looking closer at the animation by slowing it down it looks like apple inserts the cell to be added under the cells that won't be changed in the table and then moves the cells that will remain into their new positions. As a result the cell that was deleted becomes a transparent window where we see what they are actually doing under the hood.

The right approach would be to first remove the old answer and then after the animation is over add the new answer. There is a UITableViewDelegate method that is triggered after the cell animation is complete.
tableView:didEndDisplayingCell:forRowAtIndexPath:
Inserting the new answer row within this delegate method will result in the correct animation.
There are a few details to keep in mind- You'll need some logic to ensure that the correct cell height is returned and that the correct number of expected rows in the section is returned. Those data source methods are called after we remove our old answer in and again when we add the new one when we call
endUpdates
on the table view
Using anything other than UITableViewRowAnimationTop results in some strange animation behavior. This is because the content view of the cell is not what is being animated.

i solve same problem in my project by hide control like:
in ~cell.m have
(void)setSelected:(BOOL)selected animated:(BOOL)animated;
{
if(selected == NO)
{
self.selectedView.hidden = YES;
self.descriptionLabel.hidden = YES;
}
else
{
self.selectedView.hidden = NO;
self.descriptionLabel.hidden = NO;
}
}
may it still helpful

Related

Change in TableViewCell to take effect immediately

I have a TableView with a custom cell. One of the subviews in the cell is a UIButton. When a user clicks on the button, I want the background to change. I get all of that working. But the problem is I cannot see the change until after I scroll the affected cell off screen and then return it on screen. But I want to see the change immediately, without the onscreen offscreen bit. How might I do that?
For a bit more about my implementation:
Inside the method (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath I have the line
....
[cell.myBtn addTarget:self action:#selector(onMyBtnTapped:) forControlEvents:UIControlEventTouchUpInside];
And then inside the onMyBtnTapped method is where I effect the color change.
So perhaps what I need to do is to redraw a specific cell from the parent view controller (?).
a bit more
I have gotten as far as getting the cell using [self.tableView cellForRowAtIndexPath:indexPath];. But now that I have the cell, I don't know how to get it to redraw itself. I do this on android all the time. I am not sure how to do it on iOS.
You can reload the UITableViewCell on button click:
[self.tableView reloadRowsAtIndexPaths:#[indexPathOfGivenCell] withRowAnimation:UITableViewRowAnimationNone];
You should be able to place the following in the method.
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
You can ask to redraw cell at visible rows:
NSArray *visibleIndexPaths = [tableView indexPathsForVisibleRows];
[self.tableView reloadRowsAtIndexPaths:visibleIndexPaths withRowAnimation: UITableViewRowAnimationFade];

Expandable list view scrolling in ios

I am using a table view as an expandable list view.I am using section as parent and cells as child.I am getting list view perfectly but the issue is that i want the expanded section view to come into view if the section at end of the screen is clicked. Currently it expands and stay there so one has to scroll it manually.
Thanks.
The easiest way to do that is to call
-[UITableView scrollToRowAtIndexPath:atScrollPosition:animated:]
But be careful. If you call that right after inserting rows for the section, the animation can look pretty bad (cells flying in from weird locations etc.) The easiest solution to that, is to do that after the row insertion animation is completed. Unfortunately, there is no callback for that and the easiest workaround is to use CATransaction callback like so:
// CATransaction is used to be able to have a callback after rows insertion is finished.
// This call opens CATransaction context
[CATransaction begin];
// This call begins tableView updates (not really needed if you only make one insertion call, or one deletion call, but in this example we do both)
[tableView beginUpdates];
// Insert and delete appropriate rows
[tableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationAutomatic];
// completionBlock will be called after rows insertion/deletion animation is done
[CATransaction setCompletionBlock: ^{
// This call will scroll tableView to the top of the 'section' ('section' should have value of the folded/unfolded section's index)
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:NSNotFound inSection:section] // you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}];
// End table view updates
[tableView endUpdates];
// Close CATransaction context
[CATransaction commit];
If you do the folding/unfolding without animation, for example using plain -[UITableView reloadData], you can safely call
-[UITableView scrollToRowAtIndexPath:atScrollPosition:animated:]
directly after -[UITableView reloadData]
like so:
[tableView reloadData];
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:NSNotFound inSection:section] // 'section' is the index of the section you want to be scrolled to the top of the screen
atScrollPosition:UITableViewScrollPositionTop
animated:YES];

Visible UITableViewCells not resizing on rotate

I have a UITableView with custom cells. When I rotate the device, the visible cells do not resize. Scrolling to new cells works fine and when I scroll back, all is well, but is there a way to get the initial, visible cells to smoothly resize on rotate?
[self.tableview reloadData] from the UITableViewController; works, but I don't think that is the optimal solution, as it requires a network call for data retrieval.
I am already calling the following code from the UITableViewController in order to layout some gradient layers I use, but this does nothing to resize the ImageView.
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
for (ProjectCell *cell in [[self.tableView.subviews firstObject] subviews]) {
cell.laidOut = NO;
[cell layoutSubviews];
}
}
Thanks
I believe this will do what you want:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
As long as the cells you want to reload are the visible cells.

iOS Table view cell not displaying properly after resize

I have set up my table view with 6 static cells and I'm trying to have one cell (the third cell) expand upon selection. The problem is, it resizes after calling reloadRowsAtIndexPaths:, but then the cell appears blank. If I scroll the cell out of view, it shows the contents properly.
When I call [tableView reloadData] the cell isn't blank, but then, I can't enjoy the animation.
Does anyone know why this is so?
Well if you set the height for a row then you need to reload the content of the cell NOT the content of the whole table. If you call [tableview reloadData] then it simply means that you redraw the whole table(without the height correction).
To reload a single cell in the table:
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
I don't know if I was the only one having the problem with a blank static cell issue.
I stumbled on a solution and that is calling [tableView reloadData] after calling reloadRowsAtIndexPaths:
Reload the respective table view row using the main thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Reload the respective table view row using the main thread.
[self.tblFiles reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:index inSection:0]]
withRowAnimation:UITableViewRowAnimationNone];
}];

UITableView callback after row deletion animation complete

I have a table with shadows above the top and below the bottom cell (using Matt Gallagher's solution here: http://cocoawithlove.com/2009/08/adding-shadow-effects-to-uitableview.html). These are added in the layoutSubviews method of the UITableView class extension.
I dynamically add and delete cells below each main cell (these provide additional data) - let's call these "detail" cells. There is only one ever open at a time. When deleting the "detail cell" beneath the last main cell, as the animation begins, the shadow flicks upwards to the last cell (above the detail cell). It does this because the layoutSubview methods considers the last cell of the table to have changed the moment the animation for deleteRowsAtIndexPaths begins (rather than when the animation ends).
So, in essence, I need a way to keep the shadow below the detail cell as its being deleted. Not sure of the best way to do this. If the UITableView no longer considers that cell to be the last cell, then I am not sure even how to get the cell (since the UITableView gets the cell thus):
NSIndexPath *lastRow = [indexPathsForVisibleRows lastObject];
if ([lastRow section] == [self numberOfSections] - 1 &&
[lastRow row] == [self numberOfRowsInSection:[lastRow section]] - 1)
{
//adds shadow below it here
}
So even trapping the start of the animation is not much use if the UITableView still thinks the main cell above the "detail" cell is the "lastObject".
Thanks for any ideas.
Try this
[CATransaction begin];
[tableView beginUpdates];
//...
[CATransaction setCompletionBlock: ^{
// Code to be executed upon completion
}];
[tableView deleteRowsAtIndexPaths: indexPaths
withRowAnimation: UITableViewRowAnimationAutomatic];
[tableView endUpdates];
[CATransaction commit];
I am sure that you can easily achieve this by using a custom table view class instead of using dependencies from external frame work just inherit from the uitable view and add subviews to it.
But if you insist to keep it this way. take a reference in your own variable before deleting it.
Swift (the idea is the same, you can of course use this in obj-c):
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.tableView.scrollToRowAtIndexPath(indexPathes, withRowAnimation: UITableViewRowAnimation.None)
}, completion: { (Bool) -> Void in
// The logic you want to execute after the animation
})

Resources