How to insert and delete cell in tableview - iphone

Actually my requirement is , I have data with me and i am showing those on my tableview cell. When i click on any cell the tableview should insert three more cell immediately after the clicked cell and when u click another cell the old one should shrink and expand once again.

You need to use these methods:
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
and
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
where you can set the paths as needed.
NSArray *paths = [NSArray arrayWithObjects: [NSIndexPath indexPathForRow:0 inSection:0], ..., nil];
Update:
You also need to update numberOfRowsInSection. You could set a BOOL in your class that tells whether the extra cells are currently showing are hidden, and then check that BOOL to know what number to return. For example:
if (extraCellsAreShowing) {
return 4;
}
else {
return 1;
}
Additionally, if you use an array of values for your table view data source, you may need to alter those values as well. I can't really give an example of that as it will depend on your specific situation.

Related

Inserting row and deleting row simultaneously. UITableView

So I have a UITableView that represents a video library. The datasource of the library is a NSMutableDictionary that contains a NSMutableArray for each of its keys. Each of the Arrays contains an Array with all the info for each value.
NSMutableDictionary -> Foreach Key a NSMutableArray that contains -> NSMutableArrays.
tableSectionDatais my datasource.
I have been trying to insert a row on the first section and delete another row in another section. This is the code I am using:
EDIT This is the new attempt. I first update the datasource and then add and delete the corresponding rows that are indexed with my dataSource.
[mainTableView beginUpdates];
NSMutableDictionary *clone = [[NSMutableDictionary alloc]
initWithDictionary:self.tableSectionData copyItems:YES];
for (NSString *key in [clone allKeys]) {
if([key isEqualToString:#"Videos available for download"]) {
for (NSArray *videoArray in [clone objectForKey:key]) {
if ([[videoArray objectAtIndex:0] isEqualToString:helper.videoName]) {
[[self.tableSectionData objectForKey:#"Downloaded videos"]
addObject:videoArray];
NSUInteger insertIndex = [[self.tableSectionData
objectForKey:#"Downloaded videos"]
indexOfObject:videoArray];
NSIndexPath *pathToInsert = [NSIndexPath
indexPathForRow:insertIndex inSection:0];
NSArray *indexesToAddition = [NSArray
arrayWithObject:pathToInsert];
[mainTableView insertRowsAtIndexPaths:indexesToAddition
withRowAnimation:UITableViewRowAnimationFade];
[[self.tableSectionData
objectForKey:#"Videos available for download"] removeObject:videoArray];
NSUInteger deleteIndex = [[self.tableSectionData
objectForKey:#"Videos available for download"] indexOfObject:videoArray];
NSIndexPath *pathToDelete = [NSIndexPath
indexPathForRow:deleteIndex inSection:1];
NSArray *indexesToDeletion = [NSArray arrayWithObject:
pathToDelete];
[mainTableView deleteRowsAtIndexPaths:indexesToDeletion
withRowAnimation:UITableViewRowAnimationFade];
}
}
}
}
[mainTableView endUpdates];
I thought this would work since I have to first delete a row if I want to insert one (in the case I want to do both.)
The specific error is the following:
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (3), 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).'
I know what the error says, that I don't have a corresponding number of rows on the section however I don't see my mistake on the code and I logged the datasource and it corresponds to the desired result.
Any help and suggestions would be greatly appreciated!
You have to do all updates inside begin/end updates methods. It's valid always if you have more than one UITableView update.
And don't forget update dataSource before endUpdates called!
[self.tableView beginUpdates];
// do your staff here, like:
[self.tableView inserRowsAtIndexPaths:indexPathsToInsert];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete];
[self.tableView endUpdates];
your mistake here - is you are using different begin/end updates sections in your code sample
Where is your dataSource?
before delete
[dataSource removeObjectAtIndex:nIndexDelete];
[mainTableView deleteRowsAtIndexPaths:indexesToDeletion
withRowAnimation:UITableViewRowAnimationFade];
before add
[dataSource inserObject:object atIndex:nIndexAdd];
[mainTableView insertRowsAtIndexPaths:indexesToAddition
withRowAnimation:UITableViewRowAnimationFade];
I think you need to reload UITableView every time you insert or delete rows with returning exact number of rows in numberOfRowsInSection ...

deleteRowsAtIndexPaths doesn't work

I'm trying to collapse and expand a UITableView section with the help of deleteRowsAtIndexPaths. Nothing seems to happen though and I can't figure out why.
NSMutableArray *tmpArray = [NSMutableArray array];
for (int i=1; i<numberOfRowsInSection; i++){
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i inSection:section];
[tmpArray addObject:tmpIndexPath];
}
[_tableView beginUpdates];
[_tableView deleteRowsAtIndexPaths: tmpArray withRowAnimation:UITableViewRowAnimationAutomatic];
[_tableView endUpdates];
I've read through a lot of related questions, but nothing I do seem to help.
Any idea of what I'm doing wrong here?
UPDATE
Seems like _tableview is null. I'm guessing that's the main reason nothing is happening. Just don't understand that, since tableview is an outlet and it's already filled with rows and sections.
How can a tableview that's filled with rows and sections be null?
deleteRowAtIndexPath:withAnimation: just tells your table how it should display the table. You need to remove this row from your actual data at the same time. Aka tableView:NumberOfRowsInSection: need to return the correct number of lines.
you might be reloading the table view with original data after calling deleteRowsAt... make sure to change update your tableView data source in order to cope with the deleted changes i.e change the number of rows per section in the method numberOfRowsInSection

Animated UITableView reload causes every cell to load at once

I have a table view with 100 cells. At first, it is empty. Then, I call:
[_tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationLeft];
As there was nothing in the table view, I need to create several cells. However, I was expecting the table view to ask for 10 (to fit the screen size) cells... not 100 !
It doesn't happen when I simply reload the table view without any animation:
[_tableView reloadData];
This issue makes the table reloading very slow: is there a way to make it ask for 10 cells only ?
Edit
Maybe I wasn't clear enough: At first, the table has no entry. Then, I add 100 entries in my data source, and ask the table to reload: there is no visible cell before the reload, so the reloadRowsAtIndexPaths solution won't work.
It sounds like you're inserting new rows in the table, rather than reloading, so why not use:
[_tableView insertRowsAtIndexPaths:... withRowAnimation:...];
You may need to insert the section first:
[_tableView insertSections:... withRowAnimation:..];
http://developer.apple.com/library/ios/ipad/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html
If you only want to reload lets say 10 cells, the following code will work.
int cellsToReload = 10;
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:cellsToReload];
for(int x = 0; x < cellsToReload; x++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:x inSection:0];
[indexPaths addObject:indexPath];
}
[self.theTableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];
In the above code when you reloaded all of the tableview, you were reloading the entire section and not just a few rows. I'm guessing your tableview only had one section.
NSArray *paths = [_tableView indexPathsForVisibleRows];
[_tableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationLeft];
will do exactly what you need - reload only visible raws, neither more, nor less.
I found out that this issue occurs only on iOS 5.1 and below. No need to file a bug as it is corrected in iOS 6. Thank you for your answers anyway !

Selecting multiple rows of a UITableView

This page
http://networkpx.blogspot.com/2009/07/multiple-row-selection-with-uitableview.html
mentions a way to implement table views that can allow multiple selections of rows.
At the time of this article, it seems that it was not a blessed way of doing this.
Now, apparently, Apple is allowing this kind of tableViews.
The article mentions this
NSArray* selectedRows = [tableView indexPathsForSelectedRows];
as a way of getting a list of all rows selected but this is not a legal functionality of the SDK.
The big question is: how do I get a list of all rows selected, so I can perform an action with them?
thanks
EDIT
To answer some questions... this is the code I am using to discover if a row is selected, but this is giving me zero entries.
NSMutableArray *selectedRows = [[NSMutableArray alloc] init];
for (int i=0; i<[list count]; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UITableViewCell *aCell = (UITableViewCell*) [myTable cellForRowAtIndexPath:indexPath];
if (aCell.accessoryType == UITableViewCellAccessoryCheckmark) {
[selectedRows addObject:indexPath];
}
}
// selectedRows has always 0 entries... all cells give me their type as UITableViewCellAccessoryNone even those with checkmark
Apple will reject your app for for iOS 4 and below.
We found the following non-public API/s in your app:
indexPathsForSelectedRows
Create a NSMutableArray.
Whenever a table cell is selected insert the current indexpath in the array. whenever he deselects the cell remove it from the array. Final array will have everything you selected.
MultipleCheck in Table – UITableView Example with Demo
http://sugartin.info/2011/08/19/multiplecheck-in-table-uitableview/
Are you looking to have the user select multiple rows at the same time? If not you could create an array that holds each row that is selected as the user selects them.
Also for the check mark you could just subclass UITableViewCell.
How to subClass UITableViewCell and use it to not clear UILabel background color on UITabeViewCell selected?

UITableView: deleting sections with animation

Update
I have posted my solution to this problem as an answer below. It takes a different approach from my first revision.
Original Question
I previously asked a question on SO that I thought solved my issues:
How to deal with non-visible rows during row deletion. (UITableViews)
However, I now have similar problems again when removing sections from a UITableView.
(they resurfaced when I varied the number of sections/rows in the table).
Before I lose you because of the shear length of my post, let me state the problem clearly, and you can read as much as you require to provide an answer.
Problem:
If batch deleting rows AND sections from a UITableView, the application crashes, sometimes. It depends on the configuration of the table and the combination of rows and sections I choose to remove.
The log says I crashed because it says I have not updated the datasource and the table properly:
Invalid update: invalid number of rows in section 5. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted).
Now quickly, before you write the obvious answer, I assure you I have indeed added and deleted the rows and sections properly from the dataSource. The explanation is lengthy, but you will find it below, following the method.
So with that, if you are still interested…
Method that handles removal of sections and rows:
- (void)createFilteredTableGroups{
//index set to hold sections to remove for deletion animation
NSMutableIndexSet *sectionsToDelete = [NSMutableIndexSet indexSet];
[sectionsToDelete removeIndex:0];
//array to track cells for deletion animation
NSMutableArray *cellsToDelete = [NSMutableArray array];
//array to track controllers to delete from presentation model
NSMutableArray *controllersToDelete = [NSMutableArray array];
//for each section
for(NSUInteger i=0; i<[tableGroups count];i++){
NSMutableArray *section = [tableGroups objectAtIndex:i];
//controllers to remove
NSMutableIndexSet *controllersToDeleteInCurrentSection = [NSMutableIndexSet indexSet];
[controllersToDeleteInCurrentSection removeIndex:0];
NSUInteger indexOfController = 0;
//for each cell controller
for(ScheduleCellController *cellController in section){
//bool indicating whether the cell controller's cell should be removed
NSString *shouldDisplayString = (NSString*)[[cellController model] objectForKey:#"filteredDataSet"];
BOOL shouldDisplay = [shouldDisplayString boolValue];
//if it should be removed
if(!shouldDisplay){
NSIndexPath *cellPath = [self indexPathOfCellWithCellController:cellController];
//if cell is on screen, mark for animated deletion
if(cellPath!=nil)
[cellsToDelete addObject:cellPath];
//marking controller for deleting from presentation model
[controllersToDeleteInCurrentSection addIndex:indexOfController];
}
indexOfController++;
}
//if removing all items in section, add section to removed in animation
if([controllersToDeleteInCurrentSection count]==[section count])
[sectionsToDelete addIndex:i];
[controllersToDelete addObject:controllersToDeleteInCurrentSection];
}
//copy the unfiltered data so we can remove the data that we want to filter out
NSMutableArray *newHeaders = [tableHeaders mutableCopy];
NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease];
//removing controllers
int i = 0;
for(NSMutableArray *section in newTableGroups){
NSIndexSet *indexesToDelete = [controllersToDelete objectAtIndex:i];
[section removeObjectsAtIndexes:indexesToDelete];
i++;
}
//removing empty sections and cooresponding headers
[newHeaders removeObjectsAtIndexes:sectionsToDelete];
[newTableGroups removeObjectsAtIndexes:sectionsToDelete];
//update headers
[tableHeaders release];
tableHeaders = newHeaders;
//storing filtered table groups
self.filteredTableGroups = newTableGroups;
//filtering animation and presentation model update
[self.tableView beginUpdates];
tableGroups = self.filteredTableGroups;
[self.tableView deleteSections:sectionsToDelete withRowAnimation:UITableViewRowAnimationTop];
[self.tableView deleteRowsAtIndexPaths:cellsToDelete withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
//marking table as filtered
self.tableIsFiltered = YES;
}
My guess:
The problem seems to be this: If you look above where I list the number of cells in each section, you will see that section 5 appears to increase by 1. However, this is not true. The original section 5 has actually been deleted and another section has taken its place (specifically, it is old section 10).
So why does the table view seem not to realize this? It should KNOW that I removed the old section and should not expect a new section that is now located at the old section's index to be bound by the deleted section's number of rows.
Hopefully this makes sense, it is a little complicate to write this out.
(note this code worked before with a different number of rows/sections. this particular configuration seems to give it issues)
I’ve run into this problem before. You are trying to delete all rows from a section and then, in addition, that now empty section. However, it is sufficient (and proper) to remove that section only. All rows within it will be removed as well. Here is some sample code from my project that handles deletion of one row. It needs to determine whether it should remove only this row from a section or delete the entire section if it is the last remaining row in that section:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// modelForSection is a custom model object that holds items for this section.
[modelForSection removeItem:[self itemForRowAtIndexPath:indexPath]];
[tableView beginUpdates];
// Either delete some rows within a section (leaving at least one) or the entire section.
if ([modelForSection.items count] > 0)
{
// Section is not yet empty, so delete only the current row.
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
else
{
// Section is now completely empty, so delete the entire section.
[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
[tableView endUpdates];
}
}
I notice that you're deleting the sections from the table first, and then deleting rows.
I know there's a complicated discussion of batch insertion and deletion for UITableViews in the Table View Programming Guide, but it doesn't specifically cover this.
I think what's happening is that deleting the sections is causing the row deletions to refer to the wrong row.
i.e. you want to delete section #2 and row #1 from section #4... but after you've deleted section #2, the old section #4 is now the third section, so you when you delete with the old NSIndexPath of (4, 1) you're deleting some random different row that may not exist.
So I think the fix might be as simple as swapping those two lines of code, so you're deleting the rows first, then the sections.
So finally here is my solution to this issue.
This method can be applied to tables of any size, any number of sections (as far as I can tell)
As before I have modified Matt Gallagher's tableview Code which places cell-specific logic in a separate cell controller. However, you can easily adapt this method to a different model
I have added the following (relevant) ivars to Matt's code:
NSArray *allTableGroups; //always has a copy of every cell controller, even if filtered
NSArray *filteredTableGroups; //always has a copy of the filtered table groups
Matt's original ivar:
NSArray *allTableGroups
…always points to one of the above arrays.
This can probably be refactored and improved significantly, but I haven't had the need. Also, if you use Core Data, NSFetchedResultsController makes this easier.
Now on to the method (I am trying to comment as much as I can):
- (void)createFilteredTableGroups{
//Checking for the usual suspects. all which may through an exception
if(model==nil)
return;
if(tableGroups==nil)
return;
if([tableGroups count]==0)
return;
//lets make a new array to work with
NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease];
//telling the table what we are about to do
[self.tableView beginUpdates];
//array to track cells for deletion animation
NSMutableArray *indexesToRemove = [NSMutableArray array];
//loop through each section
for(NSMutableArray *eachSection in tableGroups){
//keeping track of the indexes to delete for each section
NSMutableIndexSet *indexesForSection = [NSMutableIndexSet indexSet];
[indexesForSection removeAllIndexes];
//increment though cell indexes
int rowIndex = 0;
//loop through each cellController in the section
for(ScheduleCellController *eachCellController in eachSection){
//Ah ha! A little magic. the cell controller must know if it should be displayed.
//This you must calculate in your business logic
if(![eachCellController shouldDisplay]){
//add non-displayed cell indexes
[indexesForSection addIndex:rowIndex];
}
rowIndex++;
}
//adding each array of section indexes, EVEN if it is empty (no indexes to delete)
[indexesToRemove addObject:indexesForSection];
}
//Now we remove cell controllers in newTableGroups and cells from the table
//Also, each subarray of newTableGroups is mutable as well
if([indexesToRemove count]>0){
int sectionIndex = 0;
for(NSMutableIndexSet *eachSectionIndexes in indexesToRemove){
//Now you know why we stuck the indexes into individual arrays, easy array method
[[newTableGroups objectAtIndex:sectionIndex] removeObjectsAtIndexes:eachSectionIndexes];
//tracking which cell indexPaths to remove for each section
NSMutableArray *indexPathsToRemove = [NSMutableArray array];
int numberOfIndexes = [eachSectionIndexes count];
//create array of indexPaths to remove
NSUInteger index = [eachSectionIndexes firstIndex];
for(int i = 0; i< numberOfIndexes; i++){
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:sectionIndex];
[indexPathsToRemove addObject:indexPath];
index = [eachSectionIndexes indexGreaterThanIndex:index];
}
//delete the rows for this section
[self.tableView deleteRowsAtIndexPaths:indexPathsToRemove withRowAnimation:UITableViewRowAnimationTop];
//next section please
sectionIndex++;
}
}
//now we figure out if we need to remove any sections
NSMutableIndexSet *sectionsToRemove = [NSMutableIndexSet indexSet];
[sectionsToRemove removeAllIndexes];
int sectionsIndex = 0;
for(NSArray *eachSection in newTableGroups){
//checking for empty sections
if([eachSection count]==0)
[sectionsToRemove addIndex:sectionsIndex];
sectionsIndex++;
}
//updating the table groups
[newTableGroups removeObjectsAtIndexes:sectionsToRemove];
//removing the empty sections
[self.tableView deleteSections:sectionsToRemove withRowAnimation:UITableViewRowAnimationTop];
//updating filteredTableGroups to the newTableGroups we just created
self.filteredTableGroups = newTableGroups;
//pointing tableGroups at the filteredGroups
tableGroups = filteredTableGroups;
//invokes the animation
[self.tableView endUpdates];
}
I saw this same exact error as the result of prematurely releasing the background view of my custom tableview cell.
With NSZombieEnabled I got a an exception being thrown way down below an internal call to a function to prepare the cell for reuse. Without NSZombieEnabled, I was getting the Internal consistency error.
Incidentally when I fixed the retain/release issue on the background view of the cell, I was able to delete the last row of the section without having to delete the section explicitly.
Moral of the story: This error just means something bad is happening when you try to delete, and one of the things that happens when you delete is the cell gets prepared for reuse, so if you are doing anything custom with your tableview cells, look for a possible error there.
I suspect that you are forgetting to remove the object representing the section from your internal storage, so that the -numberOfSectionsInTableView: method is still returning 1 after all sections are deleted.
That's exactly what I was doing wrong when I had the same crash!
A much simpler way to address this is to update your data source, then call reloadSections
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
This will reload a single section. Alternatively you could use indexSetWithIndexesInRange: to reload multiple sections simultaneously.
or just do this
- (void)tableView:(UITableView *)tv
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if(editingStyle == UITableViewCellEditingStyleDelete) {
//Delete the object from the table.
[directoriesOfFolder removeObjectAtIndex:indexPath.row];
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
}
directories of folder being your Array! Thats is all above codes didnt work for me! This is less expensive to do and just makes sense!