Animate the insertion of a new section in UITableVIew - ios

I have this button on my view, and when I press it I insert a new section in my table view ( I have a logical condition in my
-(NSInteger) numberOfSectionsInTableView:(UITableView*)tableView
{
if (slide==TRUE) return 2;
return 1;
}
And also in my -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath. My section is added as it should, but I have read somewhere that this can be animated, because when I press my button the section is added but with no animation. I think I should use this -(void)insertSections:(NSIndexSet*)sections withRowanimation(UITableViewRowAnimation) animation but I haven't found a proper example on the web.

Manipulating rows and sections into UITableView using animation is made pretty easy using the following API's (emphasis on the last two):
insertRowsAtIndexPaths:withRowAnimation:
insertSections:withRowAnimation:
deleteRowsAtIndexPaths:withRowAnimation:
deleteSections:withRowAnimation:
beginUpdates
endUpdates
Updating the Data Model
You should update your data model anywhere between the beginUpdates and endUpdates methods. It doesn't matter if you update your data model before or after insertion or deletion methods just so long as you do it between the beginUpdates and endUpdates methods.
Do not Insert Rows for New Sections You are Inserting
When adding a new section using the insertSections:withRowAnimation: method, you do not need to call insertRowsAtIndexPaths:withRowAnimation: to add rows into it. The insertRowsAtIndexPaths:withRowAnimation: method is just for animating an existing section changing. Similarly, you do not need to call deleteRowsAtIndexPaths:withRowAnimation: when you remove a section using deleteSections:withRowAnimation:.
Regarding Deleting and Inserting:
I generally do these in separate beginUpdates: endUpdates calls, however you can insert and delete at the same time. Just be aware that the UITableView will first try to delete rows / sections and then try to insert them regardless of the order you do it in your code.
Discussion part of deleteRowsAtIndexPaths:withRowAnimation:
Note the behavior of this method when it is called in an animation
block defined by the beginUpdates and endUpdates methods. UITableView
defers any insertions of rows or sections until after it has handled
the deletions of rows or sections. This happens regardless of ordering
of the insertion and deletion method calls. This is unlike inserting
or removing an item in a mutable array, where the operation can affect
the array index used for the successive insertion or removal
operation. For more on this subject, see “Batch Insertion and Deletion
of Rows and Sections” in Table View Programming Guide for iOS.
Example of inserting into a section:
int indexOfNewSection = 4; // TODO: change to meaningful value
[self.tableview beginUpdates];
[self.tableview insertSections:[NSIndexSet indexSetWithIndex:indexOfNewSection]
withRowAnimation:UITableViewRowAnimationAutomatic];
// Update data model
[self.sections insertObject:sectionObj atIndex:indexOfNewSection];
[self.tableview endUpdates];

UITableViewRowAnimation is an enum declared at the top of UITableView.h You can also view it in the UITableView reference. It's smallish, so I'll just paste it!
typedef enum {
UITableViewRowAnimationFade,
UITableViewRowAnimationRight, // slide in from right (or out to right)
UITableViewRowAnimationLeft,
UITableViewRowAnimationTop,
UITableViewRowAnimationBottom,
UITableViewRowAnimationNone, // available in iOS 3.0
UITableViewRowAnimationMiddle, // available in iOS 3.2. attempts to keep cell centered in the space it will/did occupy
UITableViewRowAnimationAutomatic = 100 // available in iOS 5.0. chooses an appropriate animation style for you
} UITableViewRowAnimation;
Basically it tells the table view from which direction you want the rows/sections to be animated in/out. A little experimenting will demonstrate the effect of each.
For example, inserting a row with UITableViewRowAnimationTop will trigger an animation which gives the impression of a row coming into the table view from a space immediately above that of its final destination in the table view.
So your insertion might look like:
-(void)sliderValueChanged:(id)slider {
slide = slider.on;
[tableView beginUpdates];
if (slider.on) {
[tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop];
// TODO: update data model by inserting new section
} else {
[tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop];
// TODO: update data model by removing approprite section
}
[tableView endUpdates];
}
And you will have to be sure that your delegate and data source provide info that is consistent with your assertion to insert sections/rows. From your question, it looks like you have done so.
EDITS:
I don't think you have to call reloadData. UITableView requires that your data model reflect the changes you make with with the insert/delete methods. So that, for example, if, before a call to insertSections:withRowAnimation: (with which you are inserting a single section), your numberOfSectionsInTableView: method returned 1, then, after the call, it must return 2. To do otherwise throws an exception. It is this enforcement of consistency that allows you (again, I think) to avoid the call to reloadData - the necessary data is being reloaded by the whole beginUpdates: endUpdates: transaction, and any updates to the model you make during that transaction must match one-for-one your insert/delete calls.
Clear as mud?
UPDATES
If you were programming explicit animations, then I would say that you could do it in the 'completion handler' (or just program the animation directly for that matter), but that is not available to you here. I think you can wrap the button-presenting code in its own method in the view, then set a timer to call it after a short amount of time, say, .2 seconds (you'll have to experiment to see what looks good). e.g.
[NSTimer scheduledTimerWithTimeInterval:.2 target:self selector:#selector(presentButton) userInfo:nil repeats:NO];
That should do the trick.

- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
This method pointed out by will definitely do the magic.
Probably you can try this:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
Writing straight out of my mind.. hope this helps...
Please note:
Any of the UITableViewRowAnimation can be used.
sectionIndex in the mentioned code is an NSInteger. Probably in your case 1.

Related

iOS UITableView behaviour broken when not active

My current set up is the following:
Root Tab Bar:
Collection view with magazines
Bookmarks (with a table view)
Others
You can add a bookmark from a magazine in the collection view and also remove it from there.
The behaviour I'm seeing is the following:
I start the application, the table view queries the number of sections, number of cells, but not the cellForRowAtIndexPath. I could understand why, as there is no cell in the active view, so no data should be loaded.
When I add a bookmark from the collection view, it adds it to the array (via a notification) and requests the tableview to be reloaded. As there isn't an initial entry, it goes through the motions described above. When I press it again to remove the bookmark the entry is removed from the array. This is where it gets interesting because the first thing the table calls is not the number of sections or rows but the cellForRowAtIndexPath. As the array is empty, the application crashes on a request for data on index 0.
My question is why does the cell creation get called in that order? Is there any way to avoid it?
If you changed the section, try calling - (void)reloadSections:(NSIndexSet *)sections before you call reloadData
https://developer.apple.com/library/ios/DOCUMENTATION/UIKit/Reference/UICollectionView_class/index.html#//apple_ref/occ/instm/UICollectionView/reloadSections:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationLeft];
The reason this was happening is because I was attempting to change something about the table before I reloaded the data.
[self.tableView setSeparatorStyle: !_helpText.hidden ? UITableViewCellSeparatorStyleNone : UITableViewCellSeparatorStyleSingleLine];
[self.tableView reloadData];
That was for removing the lines so a message can be displayed. However that update was using old data as reloadData had not been called.
[self.tableView reloadData];
[self.tableView setSeparatorStyle: !_helpText.hidden ? UITableViewCellSeparatorStyleNone : UITableViewCellSeparatorStyleSingleLine];
Reversing them fixed the issue.

When reloading a UITableView section, all sections flash

I'm using a UITableViewController, and am using:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sectionNumber] withRowAnimation:UITableViewRowAnimationNone];
to reload an individual section of the tableview (to animate the insertion of new cells). The issue is that all of the sections, and their cells, flash white briefly every time this call is made. This does not occur if I use
[self.tableview reloadData];
but does occur no matter which row animation I use.
I'm aware that I can use insertRowsAtIndexPaths:withRowAnimation:, but I currently have a race condition that doesn't allow me to use that. I will, at some point, fix that, but in the meantime I would like to know why all sections cells flash while I'm reloading a single section. In addition, if I can turn off the flash and just animate the insertion / deletion of cells, that would be ideal.
I believe that it flashes because when you reload a specific section, it still has to recalculate the size of all the visible sections. Hence the flash. Without using insertion/deletion (because it knows the table will only change by one), you can't get past that.
If you are just trying this in the simulator though, it is possible that the flash will either disappear or be less noticeable when loaded on an actual device.
May be it's too late. But it could help someone.
Obj-c:
[UIView performWithoutAnimation:^{
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone];
}];
Swift 5:
UIView.performWithoutAnimation {
self.tableView.reloadSections(NSIndexSet(index: indexPath.section), with: .none)
}

Middle animation in grouped table view looks horrible

I've found similar question, but there is no answer (sorry, answer just doesn't work).
So I have grouped table and I want to animate content update instead of doing [tableView reloadData].
I do that by using this piece of code:
// Data source already updated here, but reloadData wasn't called
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationMiddle];
[self.tableView endUpdates];
I uploaded 2 examples of the animation:
Plain: http://cl.ly/3u1M3l1w3V3J (slow motion)
Grouped: http://cl.ly/1O3Z2M280n0z (slow motion)
As you can see difference is huge.
I don't change my code at all, just change tableView style in the storyboard.
Does it men that there is no other way then subclassing UITableView and UITableViewCell and implement my very own animation using CoreAnimation?
Implementing your own animation with CoreAnimation shouldn't be necessary when it comes to animating the rows of the table.
UITableView supports much more advanced animations than simply reloading a section and I suggest that you take a look at them.
Since you are shuffling the rows in your videos you should take a look at moveRowAtIndexPath:toIndexPath: (on UITableView). You put the calls to it within beginUpdates and endUpdates.
By knowing the order before and after the re-shuffle you can move all the rows into their new places and have them slide into their correct place.
It will take some thinking to figure out where each row should go but it will be much easier than rolling your completely custom solution.

UITableView's reloadRowsAtIndexPaths: seems to break insertRowsAtIndexPaths:

I have a UITableView into which the user can insert new rows. When this happens, I want to reload all of the old rows in the table. One solution would be to just call reloadData as soon as the insertion takes place, which totally works, but this means I don't get the insertion animation.
So when the user hits the "add row" button, I call reloadRowsAtIndexPaths: with every index path except the one just inserted. Then I call insertRowsAtIndexPaths: with only the newly inserted row. Reasonable, right?
This causes the app to crash with the following explanation:
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
This happens, as you can see in this example, even when reloadRowsAtIndexPaths is passed an empty array of index paths.
Ah! I need to wrap the two calls with beginUpdates and endUpdates. Fair enough. But now the animation is completely broken.
I'm performing the reload with a UITableViewRowAnimationFade and the insertion with a UITableViewRowAnimationAutomatic. But during the animation, the heights of every row changes, creating this weird flickery effect that looks just terrible. What's the correct way to animate these changes?
Edit:
From the docs for reloadRowsAtIndexPaths:withRowAnimation::
Reloading a row causes the table view to ask its data source for a new cell for that row. The table animates that new cell in as it animates the old row out. Call this method if you want to alert the user that the value of a cell is changing. If, however, notifying the user is not important—that is, you just want to change the value that a cell is displaying—you can get the cell for a particular row and set its new value.
I think that, in my application, manually updating each cell is the right way to go. However, I am still perturbed by this bizarre animation bug, and would like to know what the cause of it is / what I would do if I did "want to alert the user that the value of the cell is changing."
A common reason for getting that error is, as the docs say, the datasource is asked for a cell. It might be less clear that it is asked twice...once to provide the initial data, again to provide the final data. That implies that the tableView:numberOfRowsInSection: method must return the old values before beginUpdates and the new values after endUpdates (or if you're using a shortcut method, before and after the call). Don't forget about numberOfSectionsInTableView either if it is relevant.
Example:
numberOfRows... return [array count];
// Incorrect
[array addObject:object];
[tableView beginUpdates];
[tableView insertRow..];
[tableView endUpdates];
// Correct
[tableView beginUpdates];
[array addObject:object];
[tableView insertRow..];
[tableView endUpdates];
For your specific case, I would recommend doing the insertion, then calling reloadData on the whole tableview so long as that doesn't mess up your animation or anything else.
You don't need to have any operations with exactly cells. All what you need - is change your datasource array, then call reload data/row/section, and you'll get changed data in your table view, with added rows.

UITableView reloadRowsAtIndexPaths performing animation when it shouldn't

I'm using a UITableView in my iOS app, and have been seeing a strange issue recently.
Suppose my table is structured as follows:
section 1 header
row
section 2 header
section 3 header
row
row
...
(Note that section 2 has no rows)
I'm performing updates to the rows in my table via
[self.tv beginUpdates];
[self.tv reloadRowsAtIndexPaths:ip withRowAnimation:UITableViewRowAnimationNone];
[self.tv endUpdates];
I don't want any animations taking place. I just want the row to update. The issue is that this strategy works for every row and section in the my table except section 3, row 1: the first row of the last section. When I update this row (which is indeed using the correct indexPaths), rather than get no animation, the row does this little jump, like it's sliding in a new row to replace the old one or something. The row slides up ever so slightly, then back down, as if I was inserting a row. I'm guessing it has something to do with the header calculations, but I do return correct values for heightForHeaderInSection.
Has anyone seen this behavior?
I wonder if the beginUpdates and endUpdates are necessary in this reload only scenario.
I had the same problem. The solution was to fetch the cell from the table using:
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath]
and then refresh it manually using a custom setup method or by simply calling:
[cell setNeedsLayout]
For more info, see:
UITableView reloadRowsAtIndexPaths graphical glitch

Resources