I'm having quite a bit of pain inserting and deleting UITableViewCells from the same UITableView!
I don't normally post code, but I thought this was the best way of showing where I'm having the problem:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 5;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (iSelectedSection == section) return 5;
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//NSLog(#"drawing row:%d section:%d", [indexPath row], [indexPath section]);
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if (iSelectedSection == [indexPath section]) {
cell.textColor = [UIColor redColor];
} else {
cell.textColor = [UIColor blackColor];
}
cell.text = [NSString stringWithFormat:#"Section: %d Row: %d", [indexPath section], [indexPath row]];
// Set up the cell
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic -- create and push a new view controller
if ([indexPath row] == 0) {
NSMutableArray *rowsToRemove = [NSMutableArray array];
NSMutableArray *rowsToAdd = [NSMutableArray array];
for(int i=0; i<5; i++) {
//NSLog(#"Adding row:%d section:%d ", i, [indexPath section]);
//NSLog(#"Removing row:%d section:%d ", i, iSelectedSection);
[rowsToAdd addObject:[NSIndexPath indexPathForRow:i inSection:[indexPath section]]];
[rowsToRemove addObject:[NSIndexPath indexPathForRow:i inSection:iSelectedSection]];
}
iSelectedSection = [indexPath section];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:rowsToRemove withRowAnimation:YES];
[tableView insertRowsAtIndexPaths:rowsToAdd withRowAnimation:YES];
[tableView endUpdates];
}
}
This code creates 5 sections, the 1st (indexed from 0) with 5 rows. When you select a section - it removes the rows from the section you had previously selected and adds rows to the section you just selected.
Pictorally, when I load up the app, I have something like this:
http://www.freeimagehosting.net/uploads/1b9f2d57e7.png http://www.freeimagehosting.net/uploads/1b9f2d57e7.png
Image here: http://www.freeimagehosting.net/uploads/1b9f2d57e7.png
After selecting a table row 0 of section 2, I then delete the rows of section 1 (which is selected by default) and add the rows of section 2. But I get this:
http://www.freeimagehosting.net/uploads/6d5d904e84.png http://www.freeimagehosting.net/uploads/6d5d904e84.png
Image here: http://www.freeimagehosting.net/uploads/6d5d904e84.png
...which isn't what I expect to happen! It seems like the first row of section 2 somehow remains - even though it definitly gets deleted.
If I just do a [tableView reloadData], everything appears as normal... but I obviously forefit the nice animations.
I'd really appreciate it if someone could shine some light here! It's driving me a little crazy!
Thanks again,
Nick.
Struggled to get this to work. Here's my code to add a row to my tableView:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[tableView beginUpdates];
[dataSource insertObject:[artistField text] atIndex:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[tableView endUpdates];
I seem to remember that numberOfRowsInSection: will get called when you call deleteRows or insertRow, you need to be really careful that the reality numberOfRowsInSection cliams matches your changes. In this case you may want to try moving the iSelectedSection = [indexPath section]; line to after the endUpdates.
I don't remember where I read this but I believe you shouldn't perform table row updates (insertions and deletions) from inside one of the table view delegate functions. I think a better alternative would be to do a performSelectorOnMainThread passing along the necessary information needed to perform the updates as an object. Something like:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// ....
[self performSelectorOnMainThread: #selector(insertRows:)
withObject: someObjectOrNil]; // double check args
}
- (void) insertRows: (NSObject*)someObjectOrNil {
[tableView beginUpdates];
// update logic
[tableView endUpdates];
// don't call reloadData here, but ensure that data returned from the
// table view delegate functions are in sync
}
In the code you posted, your loop index runs from 0 to 4, which suggests that it would delete all of the rows in section 1, and then add five new rows to section 2. Since each section already has a row 0, this would add a second instance of section 2, row 0 to the table.
I would suggest having your loop run from 1 to 4:
for (int i=1; i<5; i++)
{
// ...
}
FYI: This bug seems to have been fixed completely with the 2.2 iPhone update.
Thanks Apple!
Nick.
Related
I have a sectioned tableView that I want my user to select one item from the table. When they select the item, a check should appear next to the item (using UITableViewCellAccessoryCheckmark). If they had made a previous selection, the check should be removed from the previously selected row. Here is the code I am using:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int newRow = [indexPath row];
int oldRow = [lastIndexPath row];
if (newRow != oldRow || newRow == 0)
{
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
[lastIndexPath release];
lastIndexPath = indexPath;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
lastIndexPath is declared privately in the .h file.
This code works great for a small list that is not sectioned. But in a large table that is sectioned, it puts random check marks in rows in other sections. It is almost as if the cellForRowAtIndexPath is ignoring the section in indexPath.
The code also crashes if I select a row that is greater than the number of rows in the smallest section.
Here is the code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *itemSection = [items objectForKey:key];
static NSString *SectionsTableIdentifier = #"SectionsTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:SectionsTableIdentifier] autorelease];
}
NSArray *rowLabel = [itemSection objectAtIndex:row];
cell.textLabel.text = [rowLabel objectAtIndex:1];
NSString *detText = [rowLabel objectAtIndex:0];
detText = [detText stringByAppendingString:#" $"];
detText = [detText stringByAppendingString:[rowLabel objectAtIndex:2]];
cell.detailTextLabel.text = detText;
return cell;
}
One problem you are having here is how you are saving the indexPath in lastIndexPath. You need to retain the indexPath you are saving in lastIndexPath. the indexPath passed into this method is autoreleased so it will likely get released out from under you if you don't retain it. This could be causing your crash.
This is probably exactly what you are looking for. I developed this for one of my apps. Enjoy ! (if so please mark as answered)
Also note that I'm using ARC, so no retain and release.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
/*
1. First we get the indexPath from the prior priorSelectedRowInteger and priorSelectedSectionInteger ivars. Note: we could use a single indexPath ivar, but we separate them into row and section here for clarity.
2. Then we reset the selectedRowInteger ivar to the currently selected row. This must be done before any rows are reloaded.
3. Then we reload only the two rows at the concerned index paths, as we have captured the indexPath of the prior selected row and the method gives us the new one. We could just simply reload the table here with [self.tableView reloadData], but it would not be animated and not as smooth.
*/
NSIndexPath *priorSelectedIndexPath = [NSIndexPath indexPathForRow:priorSelectedRowInteger inSection: priorSelectedSectionInteger];
// Now that we have the priorSelectedIndexPath, we save the new one for the next round.
self.priorSelectedRowInteger = indexPath.row;
self.priorSelectedSectionInteger = indexPath.section;
// For a changing tableView, check to make sure the priorIndexPath is still valid before trying to reload the prior row.
// NSLog(#"priorSelectedIndexPath %#", priorSelectedIndexPath);
if ((tableView.numberOfSections >= priorSelectedIndexPath.section+1) && ([tableView numberOfRowsInSection:priorSelectedIndexPath.section] >= priorSelectedIndexPath.row+1)) {
NSArray *thePriorIndexPathArray = [NSArray arrayWithObject:priorSelectedIndexPath];
[self.tableView reloadRowsAtIndexPaths:thePriorIndexPathArray withRowAnimation:UITableViewRowAnimationFade];
}
// Reload only the selected indexPath - necessary to update the text colors etc.
NSArray *theIndexPathArray = [NSArray arrayWithObject:indexPath];
[self.tableView reloadRowsAtIndexPaths:theIndexPathArray withRowAnimation:UITableViewRowAnimationFade];
}
I built a multi-selection table, I did that using willSelectRowAtIndexPath and willDeselectRowAtIndexPath. It is working fine. Now I want to programatically selectALL or Select None. Is there a way to call
[tableView selectRowAtIndexPath: [NSIndexPath indexPathForRow:i inSection:0] animated:NO scrollPosition:UITableViewScrollPositionNone];
without having the currently selected row unselected? I read in documentation, this method does not call delegate's willDeselectRowAtIndexPath.
Here's all code to allow multiple selection. The problem is now that I cannot select programmatically without deselecting previous.
- (NSIndexPath *)tableView:(UITableView *)aTableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSDictionary* friend = [friendsData objectAtIndex:[indexPath row]];
if (![selectedIndexes containsObject:friend]) {
[selectedIndexes addObject:friend];
}
else {
[selectedIndexes removeObject:friend];
[tableView deselectRowAtIndexPath:indexPath animated:NO];
return nil;
}
return indexPath;
}
- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
NSDictionary* friend = [friendsData objectAtIndex:[indexPath row]];
if (![selectedIndexes containsObject:friend]) {
return indexPath;
}
// do not select so we can have multiple select
return nil;
}
You'll have to change your data source to reflect the select all or select none, then refresh the rows in question to update them to their new status.
I can't post any specific code because it really depends on how you've set up your multi-selection.
Edit
It seems that you can select all by setting selectedIndexes to friendsData and select none by clearing the contents of selectedIndexes.
Select All:
[selectedIndexes setArray: friendsData];
Select None:
[selectedIndexes removeAllObjects];
Then you can reload your table view. If you want it to be instant, execute [tableView reloadData] otherwise you can use the reload methods with animation.
Here is method to traverse a table, it works well.
Select all the cells in UITableView
for (int i = 0; i < [ptableView numberOfSections]; i++) {
for (int j = 0; j < [ptableView numberOfRowsInSection:i]; j++) {
NSUInteger ints[2] = {i,j};
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:ints length:2];
UITableViewCell *cell = [ptableView cellForRowAtIndexPath:indexPath];
//Here is your code
}
}
I added a button on the uinavigationbar I want to use it to clear all the rows of uitablview
How can I do that?
A bit late... but try this:
Where you want to clear the table:
// clear table
clearTable = YES;
[table reloadData];
clearTable = NO;
This function should look like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(clearTable)
return 0;
(...)
}
There should be a UITableView delegate method that populates the UITableView control:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Find this method and change where the data for each cell is being pulled from. If you are currently using the indexPath to access an array of values to populate your tableView cells, you can programmatically switch to an array of empty values, or even just return empty cells from this method when the condition is right.
Simple.. set your data source (NSArray or NSDictionary) to nil and [self.tableView reloadData]!
What do you exactly mean by clearing the row? Do you still want them to be there, but without text? If yes, here's this code:
UITableViewCell *cell;
NSIndexPath *index;
for (int i = 0 ; i < count; i++) {
index = [NSIndexPath indexPathForRow:i inSection:0];
cell = [tableView cellForRowAtIndexPath:index];
cell.textLabel.text = #"";
}
If you want to delete them, you can use this code:
[uiTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:index] withRowAnimation:UITableViewRowAnimationFade];
Before you are reloading the table remove all objects from your tableView array (The array which populated your tableView) like so:
[myArray removeAllObjects];
[tableView reloadData];
The crash is occurring because you need to reset number if rows count in the -
-(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [tempArray count];
}
one way is use a flag variable as below:
-(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
if (deleting)
return rowcount;
else
return [tempArray count];
}
Also you need to modify the deleting code as below:
deleting = YES;
for (i = [uiTable numberOfRowsInSection:0] - 1; i >=0 ; i--)
{
NSIndexPath *index = [NSIndexPath indexPathForRow:i inSection:0];
[uiTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:index] withRowAnimation:UITableViewRowAnimationFade];
rowcount = i;
}
deleting = NO;
[uiTable reloadData];
in your .h file
BOOL deleting;
NSInteger rowcount;
Hope this helps...
The rowcount = i should go before the call.
[uiTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:index]
withRowAnimation:UITableViewRowAnimationFade];
Otherwise, it will crash.
tableView.dataSource = nil
tableView.reloadData()
I created sample tableview application and I have an add button above the tableview, when user pressed the add button only we want to add row to table view.
I am write code like this
- (void)viewDidLoad {
isEditing = NO;
Mutarray = [[NSMutableArray alloc]init];
[super viewDidLoad];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (isEditing)
return [Mutarray count];
else
return 0;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
[TableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
NSLog(#"Row = %d", indexPath.row);
// Configure the cell...
cell.textLabel.text = [Mutarray objectAtIndex:indexPath.row];
return cell;
}
//When add button pressed
-(IBAction)Add:(id)sender
{
isEditing = YES;
[Mutarray addObject:[NSString stringWithFormat:#"%d",[Mutarray count]]];
NSArray *insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:[Mutarray count]-1 inSection:0],
nil];
[self.TableView beginUpdates];
[self.TableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
[self.TableView endUpdates];
}
This code works fine.But problem is my tableview height is 418 , it shows only 10 row as visible. So when the 11 row was added it added in tableview but not calling this cellForRowAtIndexPath function so I'm not able to auto scroll the page... The first 10 row it calls the cellForRowAtIndexPath function.
So what I my doubts is why the cellForRowAtIndexPath function only calls visible rows?
Then how can I auto scroll my tableview?
So what I my doubts is why the
cellForRowAtIndexPath function only
calls visible rows ?
It is so for optimization reasons. If table view had created cells for all its rows it could downgrade performance dramatically. Instead of that table view creates cells only for rows that are visible (and probably a couple more to ensure smooth scrolling) and then reuses them for rows that become visible - so actually you can show hundreds of row with just, say, 10 cell objects - it is a huge save.
Then how can I auto scroll my
tableview ?
You can scroll right after you added a row in your add method:
...
[table endUpdates];
[table scrollToRowAtIndexPath:[insertIndexPaths objectAtIndex:0]
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
P.S. conventionally method and variable names in objective-c start with lowercase, it is better style to follow that guideline.
I have a very simple application with a UITableViewController. Upon EDITING, I am trying to slide a row into position 0 of the first section. The new row should have an INSERT editing style while the existing row should have a DELETE style.
I've overridden the following 4 methods:
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.editing && section == 0) {
return2;
}
return 1;
}
- (UITableViewCellEditingStyle)tableView:(UITableView*)tableView
editingStyleForRowAtIndexPath:(NSIndexPath*)indexPath {
int section = indexPath.section;
int row = indexPath.row;
if (self.editing && section == 0 && row == 0) {
return UITableViewCellEditingStyleInsert;
}
return UITableViewCellEditingStyleDelete;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
NSIndexPath *ip = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView beginUpdates];
if (editing) {
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
withRowAnimation:UITableViewRowAnimationLeft];
} else {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:ip]
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ClientsControllerCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc ] initWithStyle:UITableViewCellStyleValue1reuseIdentifier:CellIdentifier] autorelease];
}
int section = indexPath.section;
int row = indexPath.row;
if (self.editing && section == 0 && row == 0) {
cell.textLabel.text = #"Add Me";
cell.detailTextLabel.text = #"Detail text";
} else {
cell.textLabel.text = #"Test me";
cell.detailTextLabel.text = #"Detail text";
}
return cell;
}
But as soon as I go into EDIT mode, "both" cells end up with an editing style of UITableViewCellEditingStyleInsert.
If I change my logic and append the new cell to the END - then it correctly draws the cells with a DELETE style and the new cell get's an INSERT.
Either way, tableView:editingStyleForRowAtIndexPath gets invoked 4 times. In fact, if I insert the new cell into section:0 row:0, this method gets called with section:row 0:0, 0:1, 0:0, 0:0. Whereas if I append the new cell into section: row:1, this method gets called with section:row 0:0, 0:1, 0:0, 0:1.
What am I missing? I should be able to insert a row and catch it right? For some reason, I can't see section=0 row=1 come through a second time.
-Luther
There's another question on StackOverflow that appears to ask essentially the same thing: SO 1508066.
The answer there claims it's nonstandard to put the Insert row at the top; it should go at the bottom instead. I'm not sure I agree with that contention, but it's certainly the path of least resistance.