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
})
Related
I'm making a UIViewController to manage a messaging screen. I'm doing this using a UITableView and some custom cells.
To make things simpler, each cell contains:
Its chat "bubble" (a UIView subclass)
Its chat text (a UILabel)
A timestamp header label (which might be hidden)
A bottom footer label (for "Sending...", "Delivered", etc.; also might be hidden)
Because of performance concerns, I am not using auto-sizing of cell heights, but caching cell heights into an NSMutableDictionary.
When the user sends or receives a new message, I want the following to occur:
The current last message cell is reloaded, hiding its bottom label, if needed.
The new last message cell is appended at the bottom of the UITableView.
The UITableView is scrolled so that the new last cell is visible.
I can get it to where the end state of the screen is as desired, but the animations in between are really kinda funky. I have tried a whole lot of different approaches to get the animations to behave. Basically, it seems like some major reloading is happening, even though the only cell that could possibly change its height is the last cell (prior to the insertion of the new cell). Plus, I'd like to have the last cell simply "appear" in place without animation. If it does, it should be off-screen, and then I should be able to animate it on-screen.
Here's my current "user sent a new message" method:
- (IBAction)sendButtonPressed {
//Creation of the new message, into 'message' variable
[self.messages addObject:message];
int thisIndex = (int)self.messages.count - 1;
NSIndexPath *this = [NSIndexPath indexPathForRow:thisIndex inSection:0];
int prevIndex = (int)self.messages.count - 2;
NSIndexPath *prev = [NSIndexPath indexPathForRow:prevIndex inSection:0];
[self removeCachedHeightForIndex:prevIndex];
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[prev] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView insertRowsAtIndexPaths:#[this] withRowAnimation:UITableViewRowAnimationTop];
[CATransaction setCompletionBlock:^{
[self scrollToBottomAnimated:YES];
}];
[self.tableView endUpdates];
}
This appears to reload the last several cells in the UITableView. Or, at least, every cell that is visible when this is called seems to be animated in some way. Only the cell at prev is actually changing in any way.
Longterm, I might pull out the header and footer labels into different cells, but is there a way to fix this animation glitch as-is?
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];
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
I'm using a UITableView with static cells for the settings view of my application.
One of the sections of this table contains two cells, the second one only being visible when a UISwitch contained in the first one is turned on.
So the UISwitch's target is this:
- (void)notificationSwitchChanged:(id)sender
{
UISwitch* switch = (UISwitch*)sender;
self.bNotify = switch.on;
[self.settingsTable reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationTop];
}
I implemented numberOfRowsInSection: this way:
(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
if (section != 1)
return [super tableView:tableView numberOfRowsInSection:section];
if (self.bNotify)
return 2;
return 1;
}
This "works", but the animation is faulty, making the first cell disappear or slide upwards halfway below the previous section or various stuff depending on the animation type I choose. The cleanest is UITableViewRowAnimationNone but is still not perfect.
If I scroll the section out of view and back it looks normal again.
EDIT: took a few screenshots of the problem:
I tried adding beginUpdates and endUpdates before and after reloadSections but it didn't change anything. Using reloadData instead of reloadSections works but there's no animation at all.
Is it because the table's cells are static or am I missing something?
I think this method should solve your problem.
[self.tableView insertSections:<#(NSIndexSet *)#> withRowAnimation:<#(UITableViewRowAnimation)#>];
Just update the data source (you dont have to because your are updating the section count depending on some flags) and call it.
Maybe you have to use this method instead.
[self.tableView insertRowsAtIndexPaths:<#(NSArray *)#> withRowAnimation:<#(UITableViewRowAnimation)#>];
How can I cause action to be taken upon completion of a UITableview's animation?
I'm attempting to shrink a UITableview cell and remove a subview of the cell that's not visible after the shrinkage. To shrink the cell, I'm calling [tableview beginUpdates] and [tableview endUpdates], and changing the height of the cell as returned by theheightForRowAtIndexPath datasource method of the UITableview.
The challenge is, i need the tableview to complete shrinking so the subview is out of sight before I can remove the subview. if I put the code to remove the subview from the cell right after (or before or between) the to call [tableview endUpdates] then the subview is removed too soon (it doesn't wait for the animation) and it looks funny.
My thought is i'd like to be able to setup a callback that runs upon completion of the animation, and remove the subView in the callback
What about this?
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// animation has finished
}];
[tableView beginUpdates];
// shrink the cells
[tableView endUpdates];
[CATransaction commit];
Try Using NSNotifications class. Post Notifications when something is about to happen, and later after something has happened. You can use a bool in the same notification method to toggle between 'about to happen' and 'has happened'.