Unable to remove all UITableView cells with animation - ios

I can's seem to be able to remove all table rows. What seems to happen is that only the cells that are in view are removed and then the ones that were out of view move up. Also checked and my data source contains only the cels that are in view at the time of deletion. Heres the code
NSMutableArray *left = [[NSMutableArray alloc]init];
NSMutableArray *right = [[NSMutableArray alloc]init];
for(int i=0;i<dataSource.count;i++){
[dataSource removeObjectAtIndex:i];
NSIndexPath *ip = [NSIndexPath indexPathForRow:i inSection:0];
if(i%2==0)
[left addObject:ip];
else
[right addObject:ip];
}
[_view.tableView beginUpdates];
[_view.tableView deleteRowsAtIndexPaths:[NSArray arrayWithArray:left]
withRowAnimation:UITableViewRowAnimationLeft];
[_view.tableView deleteRowsAtIndexPaths:[NSArray arrayWithArray:right]
withRowAnimation:UITableViewRowAnimationRight];
[_view.tableView endUpdates];
What happens now is that only the cells in view are removed and the rest pop in after the removal (also the issue seems to pop up since the dataSource only contains the rows that are in view for some reason). What i want is to remove all the rows. Any ideas?

The problem is in the for loop condition. The dataSource.count gets smaller each iteration. Try this:
for(int i=0;i<dataSource.count;i++){
NSIndexPath *ip = [NSIndexPath indexPathForRow:i inSection:0];
if(i%2==0)
[left addObject:ip];
else
[right addObject:ip];
}
[dataSource removeAllObjects];

try something like this:
[_view.tableView beginUpdates];
UITableViewRowAnimation animation;
for(int i = dataSource.count - 1; i>= 0; i--)
{
[dataSource removeObjectAtIndex: i];
NSIndexPath* ip = [NSIndexPath indexPathForRow: i
inSection: 0];
if(i % 2 == 0)
{
animation = UITableViewRowAnimationLeft;
}
else
{
animation = UITableViewRowAnimationRight;
}
[_view.tableView deleteRowsAtIndexPaths: #[ip]]
withRowAnimation: animation];
}
[_view.tableView endUpdates];

You can do this in easy way...
[dataSource removeAllObjects];
[_view.tableView reloadData];

i think that you can put your dataSource = #[]; and [self.tableview reloadData]; and remove all cell in your tableview

Related

Clicked Header Section is scroll to top

I have the following custom headerview implementation. When user clicks on headerView and it expands to show corresponding cells.
Let's imagine I have two sections. And the first section is expanded, and second one is collapsed. When I click the second section, I want this section to scroll to the top.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
ExpandableHeaderFooterView *sectionHeaderView = [self.comboTableView dequeueReusableHeaderFooterViewWithIdentifier:SectionHeaderViewIdentifier];
if (sectionHeaderView == nil)
{
sectionHeaderView = [[ExpandableHeaderFooterView alloc] initWithReuseIdentifier:SectionHeaderViewIdentifier];
}
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(selectHeaderAction:)];
[sectionHeaderView addGestureRecognizer:tapGesture];
return sectionHeaderView;
}
-(void) selectHeaderAction :(UITapGestureRecognizer*) gestureRecognizer
{
ExpandableHeaderFooterView* cell = (ExpandableHeaderFooterView*)gestureRecognizer.view;
[self toggleSection:cell withSection: cell.section];
}
-(void)toggleSection:(ExpandableHeaderFooterView *)headerView withSection:(int)section
{
((ComboItem*)comboItemsArray[section]).expanded = !((ComboItem*)comboItemsArray[section]).expanded;
[comboTableView beginUpdates];
for (int i = 0; i< ((ComboItem*)comboItemsArray[section]).allComboItems.count; i++)
{
NSArray* rowsToReload = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:i inSection:section], nil];
[comboTableView reloadRowsAtIndexPaths:rowsToReload
withRowAnimation:UITableViewRowAnimationFade];
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:section];
[comboTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
[comboTableView endUpdates];
}
Since Fade takes about half a second to execute, you will see that flick. What you need to do is either giving a small delay to TableViewScroll, or just using:
[CATransaction begin];
[comboTableView beginUpdates];
[CATransaction setCompletionBlock: ^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:section];
[comboTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}];
for (int i = 0; i< ((ComboItem*)comboItemsArray[section]).allComboItems.count; i++)
{
NSArray* rowsToReload = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:i inSection:section], nil];
[comboTableView reloadRowsAtIndexPaths:rowsToReload
withRowAnimation:UITableViewRowAnimationFade];
}
[comboTableView endUpdates];
[CATransaction commit];

Move index path by one in TableView (iOS)

I have a chat application, in which i want to increase the index path by one when new chat data come. But it is not happening through my code I am sharing code with screen shot please help.
NSUInteger messageCount = [self numberOfMessages];
if (self.conversation && messageCount > 0) {
NSLog(#"%ld",(long)indexPathValue.row);
NSIndexPath* ip = [NSIndexPath indexPathForRow:indexPathValue.row + 1 inSection:0];
NSLog(#"%ld",(long)ip.row);
[layerChatTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
Below code allows you to go last indexpath with scroll position of bottom.
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:messageCoutnt-1 inSection:0] atScrollPosition:UITableViewScrollPositionButtom animated:NO];
Try and let me know
I am assuming that you have handled the datasource updating part and that you are updating the table view on main thread.
You can try to add new row at bottom like this:
NSInteger section = 0;
NSInteger newCount = [self tableView:self.tableView numberOfRowsInSection:section];
NSMutableArray *paths = #[].mutableCopy;
[paths addObject:[NSIndexPath indexPathForRow:newCount inSection:section]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
You can also add more than 1 row with some modifications.
Hope this helps.

iOS - UITableView Delete All Rows Within A Section

I have a UITableView with expandable sections. When a user goes to another view, I need all the expanded sections to collapse, which I'll need to put in the viewWillDisappear method.
I've found solutions only on how to delete all rows from a table view at once, but is there a way to delete all the rows from a specific section?
EDIT:
I have figured out a solution, but I'm not sure if it's optimal or can lead to inefficiencies in the future. Whenever a cell is expanded, it gets added to an NSMutableIndexSet. So in my viewWillDisappear method, I iterate over the expanded sections like so:
-(void)viewWillDisappear:(BOOL)animated
{
if (expandedSections.count != 0) {
NSLog(#"COLLAPSING CALLED");
[self.tableView beginUpdates];
NSUInteger section = [expandedSections firstIndex];
do
{
NSInteger rows;
NSMutableArray *tmpArray = [NSMutableArray array];
rows = [self tableView:self.tableView numberOfRowsInSection:section];
[expandedSections removeIndex:section];
for (int i=1; i<rows; i++) {
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i inSection:section];
[tmpArray addObject:tmpIndexPath];
}
[self.tableView deleteRowsAtIndexPaths:tmpArray withRowAnimation:UITableViewRowAnimationTop];
NSIndexPath *expandableCellIndexPath = [NSIndexPath indexPathForRow:0 inSection:section];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:expandableCellIndexPath];
cell.accessoryView = [DTCustomColoredAccessory accessoryWithColor:[self.colorHolder objectAtIndex:section] type:DTCustomColoredAccessoryTypeRight];
section = [expandedSections indexGreaterThanIndex:section];
} while (section != NSNotFound);
[self.tableView endUpdates];
}
}
Please let me know if this is a good solution or, if I'm suspecting correctly, if this will lead to slower transitions between views in the future when there are more rows in each expanded section. Any help or advice would be appreciated. Thanks.
If you want to animate changes, you will need to first update your data source (to return 0 for number of rows in the section) then remove section and add section at the same index path in one transaction between [tv beginUpdates] [tv endUpdates]
Otherwise just update the data source and reload the table on your way back to the VC (if you don't want any animations)
I did not read this in detail but surely the for loop should start at zero.
for (int i=0; i<rows; i++) {
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i inSection:section];
[tmpArray addObject:tmpIndexPath];
}
otherwise you will only delete all but the first cells in the section.

Reload section without reloading section header

I have a UITableView, where there is a UISegmentedControl in the header view. It should work exactly like in the App Store app: As the user scrolls, the title in the header scrolls off the screen but the segmentedControl sticks under the navigationBar.
When the user selects a segment, the section below the header should be reloaded with a nice UITableViewRowAnimation. However, as I call tableView:reloadSections:withRowAnimation:, the header view is animated as well, which I want to prevent, because it looks terrible.
Here's my code for this:
- (void)selectedSegmentIndexChanged:(UISegmentedControl *)sender
{
int index = sender.selectedSegmentIndex;
if (index < self.oldIndex) {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationLeft];
} else if (index > self.oldIndex) {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationRight];
}
self.oldIndex = index;
}
Anyone has an idea how to reload the section below the header without reloading the header itself?
Maybe you should try with
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationLeft] //or UITableViewRowAnimationRight
However, I'm not sure but I think it can rise some error in the case where you have less rows to reload than previously.
Edit
I think you could deal with [tableView beginUpdates] and [tableView endUpdates] to solve your problem.
For example, you have 2 arrays of data to display. Let name them oldArray and newArray.
A sample of how what you could do :
- (void)selectedSegmentIndexChanged:(UISegmentedControl *)sender
{
[self.tableView setDataSource: newArray];
int nbRowToDelete = [oldArray count];
int nbRowToInsert = [newArray count];
NSMutableArray *indexPathsToInsert = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < nbRowToInsert; i++) {
[indexPathsToInsert addObject:[NSIndexPath indexPathForRow:i inSection:section]];
}
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < nbRowToDelete; i++) {
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:section]];
}
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
}
If you are using Swift 2.0, feel free to use this extension.
Be warned: passing in the wrong oldCount or newCount will crash you program.
extension UITableView{
func reloadRowsInSection(section: Int, oldCount:Int, newCount: Int){
let maxCount = max(oldCount, newCount)
let minCount = min(oldCount, newCount)
var changed = [NSIndexPath]()
for i in minCount..<maxCount {
let indexPath = NSIndexPath(forRow: i, inSection: section)
changed.append(indexPath)
}
var reload = [NSIndexPath]()
for i in 0..<minCount{
let indexPath = NSIndexPath(forRow: i, inSection: section)
reload.append(indexPath)
}
beginUpdates()
if(newCount > oldCount){
insertRowsAtIndexPaths(changed, withRowAnimation: .Fade)
}else if(oldCount > newCount){
deleteRowsAtIndexPaths(changed, withRowAnimation: .Fade)
}
if(newCount > oldCount || newCount == oldCount){
reloadRowsAtIndexPaths(reload, withRowAnimation: .None)
}
endUpdates()
}
Try this:
BOOL needsReloadHeader = YES;
UIView *oldHeaderView = nil;
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerToReturn = nil;
if(needsReloadHeader == YES) {
headerToReturn = [[UIView alloc] init];
// ...
// custom your header view in this block
// and save
// ...
oldHeaderView = headerToReturn;
} else {
headerToReturn = oldHeaderView;
}
return headerToReturn;
}
Your just need to change 'needsReloadHeader' to 'NO' in other places.
An objective-c version of Intentss extension
#interface UITableView (Extensions)
- (void)reloadRowsInSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)rowAnimation oldCount:(NSUInteger)oldCount newCount:(NSUInteger)newCount;
#end
#implementation UITableView (Extensions)
- (void)reloadRowsInSection:(NSUInteger)sectionIndex withRowAnimation:(UITableViewRowAnimation)rowAnimation oldCount:(NSUInteger)oldCount newCount:(NSUInteger)newCount {
NSUInteger minCount = MIN(oldCount, newCount);
NSMutableArray *insert = [NSMutableArray array];
NSMutableArray *delete = [NSMutableArray array];
NSMutableArray *reload = [NSMutableArray array];
for (NSUInteger row = oldCount; row < newCount; row++) {
[insert addObject:[NSIndexPath indexPathForRow:row inSection:sectionIndex]];
}
for (NSUInteger row = newCount; row < oldCount; row++) {
[delete addObject:[NSIndexPath indexPathForRow:row inSection:sectionIndex]];
}
for (NSUInteger row = 0; row < minCount; row++) {
[reload addObject:[NSIndexPath indexPathForRow:row inSection:sectionIndex]];
}
[self beginUpdates];
[self insertRowsAtIndexPaths:insert withRowAnimation:rowAnimation];
[self deleteRowsAtIndexPaths:delete withRowAnimation:rowAnimation];
[self reloadRowsAtIndexPaths:reload withRowAnimation:rowAnimation];
[self endUpdates];
}
#end
You're reloading the section, so clearly everything in the section will be reloaded (including the header).
Why not instead place the UISegmentedControl inside UITableView's tableHeaderView? This would allow for exactly the behavior you're after.
The simple answer is just don't reload the sections animated, just use UITableViewRowAnimationNone.
Right now you're using UITableViewRowAnimationLeft and UITableViewRowAnimationRight, which slides your section in and out as well.
However, even with UITableViewRowAnimationNone, rows will still be animated if the number of cells before the update differ from the ones after the update.
Also, a nice read on this topic, here.
Cheers.
Here's another way which you could use and still use animations.
Let's say you have a dynamic DataSource, which changes when you select something, and you want to update just the rows of that section, while leaving the section header on top, untouched.
/** I get the desired handler from the handler collection. This handler is just a
simple NSObject subclass subscribed to UITableViewDelegate and UITableViewDataSource
protocols. **/
id handler = [self.tableViewHandlers objectForKey:[NSNumber numberWithInteger:index]];
/** Get the rows which will be deleted */
NSInteger numberOfRows = [self.tableView numberOfRowsInSection:sectionIndex];
NSMutableArray* indexPathArray = [NSMutableArray array];
for (int rowIndex = 0; rowIndex < numberOfRows; rowIndex++){
[indexPathArray addObject:[NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]];
}
/** Update the handler */
[self.tableView setDataSource:handler];
[self.tableView setDelegate:handler];
/** Get the rows which will be added */
NSInteger newNumberOfRows = [handler tableView:self.tableView numberOfRowsInSection:sectionIndex];
NSMutableArray* newIndexPathArray = [NSMutableArray array];
for (int rowIndex = 0; rowIndex < newNumberOfRows; rowIndex++){
[newIndexPathArray addObject:[NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]];
}
/** Perform updates */
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:newIndexPathArray withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
As a note, please stick to the specified order of operations, UITableView demands it.
If you have only one handler (datasource and delegate), it's easy to modify the above code to achieve the same results.

How to delete rows from UITableView with animation?

I am having problems with deleting rows from table view. I am using the code below when the delete button in the row was pressed:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:control.tag-100 inSection:0];
[resultList removeObjectAtIndex:indexPath.row];
[resultView beginUpdates];
[resultView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[resultView endUpdates];
//[resultView reloadData];
First row was deleted successfully but then, indexes were not correct. So when I delete the last row, it gives index out of bounds exception.
The cell generation code is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"personalizeTableCell";
PersonalizeCell *cell = (PersonalizeCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[PersonalizeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.title.text = #"text";
cell.rateView.tag = indexPath.row + 100;
return cell;
}
Where am I wrong?
UPDATE:
for (NSInteger j = 0; j < [venuesTableView numberOfSections]; ++j)
{
for (NSInteger i = 0; i < [venuesTableView numberOfRowsInSection:j]; ++i)
{
PersonalizeCell* cell = (PersonalizeCell*)[venuesTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]];
cell.rateView.tag = 100 + i;
}
}
solved my problem. Thanks to Nenad M.
Assuming your [NSIndexPath indexPathForRow:control.tag-100 inSection:0]; returns the right index path....
Did you try moving the removeObjectAtIndex call inside the begin-/end-updates "bracket"?:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:control.tag-100 inSection:0];
[resultView beginUpdates];
[resultList removeObjectAtIndex:indexPath.row];
[resultView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[resultView endUpdates];
UPDATE:
Obviously your cell.rateView.tag become wrong, after youe delete your first cell.
So after every deletion (i.e. every removeObjectAtIndex...) you must re-iterate over your remaining tableview-cells an re-assign the proper tag-value (cell.rateView.tag = indexPath.row + 100)! Otherwise your [NSIndexPath indexPathForRow:control.tag-100 inSection:0]; will return a wrong indexPath, therefore leading to your out of bounds error!
Re-assigning the tag-values:
You don't have to reload the entire table, just loop through the remaining cells an re-assign the tag-value after [resultView endUpdates];:
NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger j = 0; j < [tableView numberOfSections]; ++j)
{
for (NSInteger i = 0; i < [tableView numberOfRowsInSection:j]; ++i)
{
[cells addObject:[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]];
}
}
Now do:
for (PersonalizeCell* cell in cells)
{
cell.rateView.tag = // new calculated tag
}
(Or do the re-assignment even in the inner-loop of the first code-snippet directly.)
Here's some really typical code for the whole process, two lines from the table in the example:
Note that facebookRowsExpanded is a class variable you must have:
if ( [theCommand isEqualToString:#"fbexpander"] )
{
NSLog(#"expander button......");
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NSArray *deleteIndexPaths;
NSArray *insertIndexPaths;
facebookRowsExpanded = !facebookRowsExpanded;
// you must do that BEFORE, not AFTER the animation:
if ( !facebookRowsExpanded ) // ie, it was just true, is now false
{
deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
nil];
[tableView beginUpdates];
[tableView
deleteRowsAtIndexPaths:deleteIndexPaths
withRowAnimation: UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
else
{
insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
nil];
[tableView beginUpdates];
[tableView
insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation: UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
// DO NOT do this at the end: [_superTableView reloadData];
return;
}
NOTE: your code for numberOfRowsInSection must use facebookRowsExpanded
(it will be something like "if facebookRowsExpanded return 7, else return 5")
NOTE: your code for cellForRowAtIndexPath must use facebookRowsExpanded.
(it has to return the correct row, depending on whether or not you are expanded.)

Resources