I've got a UITableView that allows the user to delete rows in it by swiping them left (very much like the app Clear). My issue is that all the UITableViewCells below the one being deleted animate up to their new frames while the animation for the deleted cell is happening. I want it to happen after the animation for the deleted cell finishes.
My code:
//Starts updating the table
[CATransaction begin];
[CATransaction setAnimationDuration:JTTableViewRowAnimationDuration];
[CATransaction setCompletionBlock: ^{
[self.spenderTable reloadData];
}];
[tableView beginUpdates];
if (state == JTTableViewCellEditingStateLeft)
{
[self.list.spendrItems removeObjectAtIndex:indexPath.row];
//!!Animation happens here!!
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
[tableView endUpdates];
[CATransaction commit];
[tableView performSelector:#selector(reloadVisibleRowsExceptIndexPath:) withObject:indexPath afterDelay:JTTableViewRowAnimationDuration * 2];
Im also using this great UITableView.
Any ideas on how to achieve this?
Got this figured out....for anyone else interested. You can go two ways with:
Approach #1 (Wrap in animation block):
[UIView animateWithDuration:0.25 animations:^{
[[(JTTransformableTableViewCell *)[self.spenderTable cellForRowAtIndexPath:indexPath] contentView] setX:-320];
}completion:^(BOOL done){
[self.list.spendrItems removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
Remember to manipulate the datasource in the completion block to avoid an internal consistency exception.
Approach #2 (inherit from UITableView):
Override the animation like so:
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation{
for (NSIndexPath *indexPath in indexPaths)
{
UITableViewCell *cell = [self cellForRowAtIndexPath:indexPath];
[cell setFrame:CGRectMake(320, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
[UIView beginAnimations:NULL context:nil];
[UIView setAnimationDuration:1];
[cell setFrame:CGRectMake(0, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
[UIView commitAnimations];
}
}
Related
How can we change animation transition speed when deleting a row from uitableview, i checked documentation i didn't find any argument where i can specify seconds for transition speed
[self.tableViewMeeting deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationLeft];
Let me know if there is any workaround to this.
Try this:
self.tableView.layer.speed = 0.3
Documentation: https://developer.apple.com/library/prerelease/ios/documentation/GraphicsImaging/Reference/CAMediaTiming_protocol/index.html
[CATransaction begin];
[tableView beginUpdates];
//...
[CATransaction setCompletionBlock: ^{
// Code to be executed upon completion
}];
[tableView insertRowsAtIndexPaths: indexPaths
withRowAnimation: UITableViewRowAnimationAutomatic];
[tableView endUpdates];
[CATransaction commit];
Why don't you try UIView animation.
[UIView animateWithDuration:2
delay:0.2
options:UIViewAnimationOptionCurveEaseInEaseOut
animations:^{
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
completion:^(BOOL finished) {
// code
}
];
I lost a bit nerves on this case. I have UIView with few images as buttons. I would like to insert it with insertRowsAtIndexPaths:
I have no idea how to start with that, and I really searched every site on google. I even found something that works like a charm ( http://jackkwok.github.io/JKExpandTableView/ ) but I still cannot make it right.
Here is my didSelectRowAtIndexPath method:
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
int selectedRow = indexPath.row;
NSLog(#"touch on row %d", selectedRow);
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
PrototypeCell *prototypeCell =(PrototypeCell *)[tableView cellForRowAtIndexPath:indexPath];
if (prototypeCell.stretched == NO){
[UIView animateWithDuration:1 animations:^{
prototypeCell.View.frame = CGRectMake(0, 0, 320, 120);}
completion: nil];
prototypeCell.stretched = YES;}
else {
[UIView animateWithDuration:1 animations:^{
prototypeCell.View.frame = CGRectMake(0, 0, 15, 120);}
completion: nil];
prototypeCell.stretched = NO;
}
// Start a new core animation transaction
[CATransaction begin];
// Set a completion block for the animation
[CATransaction setCompletionBlock:^{
//
// Update the data model to add a new section
// [_data addObject:[DetailView init] alloc];
NSInteger item = self.thingList.count-1;
// Animate the new section apperance
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForItem:item inSection:0], nil] withRowAnimation:UITableViewRowAnimationFade];
NSLog(#"Array is: %#", _data);
// [tableView endUpdates];
}];
[CATransaction commit];
}
My head is already numb from that problem. I cannot even understand basic inserting, how I write what I would like to insert here?
Any Help appreciated!
Based on your described intent, I'm not entirely sure you need or want to insert table rows. It looks like what you are trying to achieve is to simply expand the height of a given row to reveal some extra buttons when that row is selected.
Changing the height of row is covered How to dynamically resize UITableViewCell height
Essentially you can have your PrototypeCell have some extra content (the buttons) that is usually clipped. When the view is selected, you simply change the height to make sure all of the buttons would be visible and you indicate to the tableview to reload the row that was selected.
Should you need to insert rows, this was covered in how to properly use insertRowsAtIndexPaths?
Good luck!
i am trying to create a custom animation for the deletion of table rows by overriding the deleteRowsAtIndexPaths method. i created the custom insert animation successfully and tried to reverse the animation for the deletion part but i get some errors: Assertion failure in -[UITableView _endCellAnimationsWithContext:]
and
NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.(rows after update must be equal to rows before the update).
If i don't override the deleteRows method and use tableView's default delete method, everything works fine, but not with my own method.
Here is the overriden method written in my tableViewController class:
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths
withRowAnimation:(UITableViewRowAnimation)animation
{
[[super tableView] insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates]; // Populates UITableView with data
[self.tableView beginUpdates];
for (NSIndexPath *indexPath in indexPaths) {
__block UITableViewCell *cell = [[super tableView] cellForRowAtIndexPath:indexPath];
if (cell) { // If indexPath isn't visible we'll get nil here
// Custom animation
CGRect frame = cell.frame;
frame.origin.x = cell.frame.size.width;
cell.frame = frame;
frame.origin.x = 0;
void (^animationsBlock)() = ^{
cell.frame = frame;
};
if ([[UIView class] respondsToSelector:
#selector(animateWithDuration:delay:usingSpringWithDamping:
initialSpringVelocity:options:animations:completion:)]) {
[UIView animateWithDuration:0.9
delay:0
usingSpringWithDamping:0.5
initialSpringVelocity:0
options:0
animations:animationsBlock
completion:NULL];
} else {
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:animationsBlock
completion:NULL];
}
}
}
}
deleteRowsAtIndexPathsMethod:
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths
withRowAnimation:(UITableViewRowAnimation)animation
{
[[super tableView] deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates]; // Populates UITableView with data
[self.tableView beginUpdates];
for (NSIndexPath *indexPath in indexPaths) {
__block UITableViewCell *cell = [[super tableView] cellForRowAtIndexPath:indexPath];
if (cell) { // If indexPath isn't visible we'll get nil here
// Custom animation
CGRect frame = cell.frame;
frame.origin.x = 0;
cell.frame = frame;
frame.origin.x = cell.frame.size.width;
void (^animationsBlock)() = ^{
cell.frame = frame;
};
if ([[UIView class] respondsToSelector:
#selector(animateWithDuration:delay:options:animations:completion:)]) {
[UIView animateWithDuration:0.9
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:animationsBlock
completion:NULL];
} else {
[UIView animateWithDuration:0.9
delay:0
options:UIViewAnimationCurveLinear
animations:animationsBlock
completion:NULL];
}
}
}
}
Edit: this code is inside a method, that runs whenever i modify a cell's object. I modify an object and save it to core data, then i retrieve all the updated objects using [self populateData], then i want to run the animations: delete the old cell and insert the new updated cell somewhere at the bottom of the tableView
[coreDataStack saveContext];
[self populateData];
[self.tableView beginUpdates];
int oldIndex = indexPath.row;
int newIndex = newIndexPath.row;
if(oldIndex<newIndex){
[self deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationRight];
[self insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationTop];
} else if (oldIndex>newIndex) {
[self deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationRight];
[self insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationBottom];
} else {
[self.tableView reloadRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationMiddle];
}
[self.tableView endUpdates];
I am trying to figure out a way to animate the deletion of a UICollectionViewCell similarly to springboard in iOS 7. The deleted cell should shrink into nothingness and the remaining cells should slide over to fill its place (the sliding is the important part). I have tried:
• Setting the frame of each cell to the frame of the previous cell, like so:
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
CGRect rect = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i - 1 inSection:0]].frame;
[UIView animateWithDuration:0.2 animations:^{
cell.frame = rect;
}];
But this did not work, as the cells did not move at all.
• Both of the following:
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
} completion:nil];
and
[UIView animateWithDuration:0.2 animations:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
}];
But these make the cells crossfade to their required spots, not slide.
I also found this project, which uses the following:
for (int i = index+1; i<[items count]; i++) {
SEMenuItem *item = [items objectAtIndex:i];
[UIView animateWithDuration:0.2 animations:^{
if (i < index + remainingNumberOfItemsInPage) {
int intVal = item.frame.origin.x;
if (intVal %3== 0)
[item setFrame:CGRectMake(item.frame.origin.x+2*item.frame.size.width, item.frame.origin.y-item.frame.size.height-5, item.frame.size.width, item.frame.size.height)];
else
[item setFrame:CGRectMake(item.frame.origin.x-item.frame.size.width, item.frame.origin.y, item.frame.size.width, item.frame.size.height)];
}
[item updateTag:item.tag-1];
}];
}
However, it is not in a UICollectionView, so it does not help me.
Note: I am doing this on an iPad, just in case it matters to you.
Any ideas?
Subclass UICollectionViewFlowLayout. Implement:
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath]; // might need to replace self with super
attr.transform = CGAffineTransformMakeScale(0, 0);
return attr;
}
In addition to duci9y's answer, (using attr.transform = CGAffineTransformMakeScale(0,0);), the following makes the tiles slide. I can't believe I didn't think of this before.
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[dataSource removeObjectAtIndex:indexPath.row];
} completion:nil];
Is there a way to change the duration of [table beginUpdates]/[table endUpdates] animations?
This is what I've tried, with no luck:
Option 1:
[UIView animateWithDuration:5.0 delay:0.0 options:(UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionOverrideInheritedDuration) animations:^{
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithArray:indexPaths] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
} completion:^(BOOL finished) {
}];
Option 2:
[CATransaction begin];
[CATransaction setCompletionBlock:^{
NSLog(#"I actually get called!");
}];
[CATransaction setAnimationDuration:5.0]; //but I don't work
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithArray:indexPaths] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
[CATransaction commit];
Why don't you try UIView animation.
[UIView animateWithDuration:2 delay:0.2 options:UIViewAnimationOptionCurveEaseInEaseOut animations:^{
[self.tableView beginUpdates];
[self.tableView endUpdates];
} completion:^(BOOL finished) {
// code
}];
Here is the Swift version of Gautam Jain's answer 😉:
UIView.animate(withDuration: 2.0, delay: 0.0, options: .curveEaseInOut, animations: {
self.tableView.beginUpdates()
// ...
self.tableView.endUpdates()
}) { isFinished in
// ...
}
#Gautam Jain 's solution is great. However, it has a problem, at least in iOS 9: the completion block will be executed at once but not when the animation completes.
I usually do like below, with a little more code but works better.
[UIView beginAnimations:#"animation" context:nil];
[UIView setAnimationDuration:0.25];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
// completion block
}];
[self.tableView beginUpdates];
// updates
[self.tableView endUpdates];
[CATransaction commit];
[UIView commitAnimations];