How can I programmatically add or remove a cell from a grouped section?
I created a grouped tableview using static cells in storyboard. Inside the storyboard I set the number of rows using the Attribute Inspector panel. For example, for section 1, I define 3 rows. Then using an NSMutableArray of 3 items, I can properly load values into each section correctly at startup.
I ultimately want the ability to add/remove a cell at runtime. That part hasn't been coded yet but to simulate adding a new cell scenario, I added a new item to the array in the code but did not increase the row count for the section in the Attribute Inspector panel. I had hoped that I would not need to make any other changes to accomodate the new item since inside the numberOfRowsInSection method, I'm returning the count of the array for the specific section.
This is the error message that I get when I re-ran the code:
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 2]'
Apparently I need to somehow specify an additional row count. Can anyone shed some light on how I can do this at runtime? Thanks.
You should not do it through attribute inspector. it abstains you to do dynamic insertion and deletion of cells.
try
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [yourArray count];
}
else do this.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(section==0){
return 3;
}else if(section==1){
return 4;
}
return 0;
}
and so on for different section if u have many section..
you can reload the whole table when you array from which data is read is updated..
[tableView reloadData];
but a better way to do it is to insert row or multiple rows like this in a block.
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:yourIndexPath withRowAnimation:UITableViewRowAnimationRight];
[tableView endUpdates];
you can also add animation to add cell from right or lect or top or bottom.
I hope this helps you!!
Cheers!!
I guess that you hardcoded the number of rows for your section. Because looking at the error it seems that your section is expecting 4 object but your data source contains only 3.
Inside your :
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
you should return something like [myArray count], so adding or delting rows shouldn't be a problem if you add or remove the object from your array.
You have Predefined methods in UITableview for Inserting and deleting row, For insertion we can user insertRowAtIndexPath method and for deletion use deleteRowAtIndexPath methods.
Related
In one section of my app I have a UITableView which is working fine right now. I would like to set row 0 cell.textLabel.text to #"Some string". Once row 0 has been set I would then like to load the rest of the rows from an array. Currently on load my array populates the table view but I'm trying to set row 0 as a sticky. The closest example I can think of is a forum topic that is set to stay at the top. My array is constructed of returned data from a web service call.
It's been a while since I've messed with table views, and I'm having a blank on this one.
The table view is 1 section, and I get the rows by counting the elements in the array. Since I would like to create an additional cell (row 0) I would call [array count] + 1. I don't know if this approach is the best one which is why I'm reaching out to the community here.
Any insight or a shove in the right direction would be great at this point.
You're on the right track:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [array count]+1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if ([indexPath row] == 0) {
// Code for first
[[cell textLabel] setText:#"First cell"];
} else {
[[cell textLabel] setText:[array objectAtIndex:[indexPath row]-1]];
}
return cell;
}
If you want the top of your table to be "sticky", why not consider using that string as a section header or title? In this case, the header stays visible at all times until the next section (e.g. if you had two sections, that is) is fully on the screen.
In any event, in one of my current projects I'm required to do roughly the same thing that you're doing and I have a static string being returned in row 0 (which scrolls off the top of screen when the table view scrolls down).
And in my UITableViewDataSource method, I always add one for the static cell to the number of objects in my array and in my "cellForRowAtIndexPath:" method, I increment the row by one when the indexPath.row is not zero. And if it is zero, I return my static string.
And dark_knight provides some nice sample code that illustrates what I was describing to you. So +1 to him/her.
In my application I am using UITableview. In that TableView I am displaying an array that contains dictionaries. The last row of the TableView contains one button which indicates user load more events. If the user clicks on the button we need to get data from a service and load the data in the UITableview.
For that purpose I am trying to implement it like the code below:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section**
{
if (ifMore)
{
return [totalArray count];
}
else
{
return [tableArray count];
}
return 0;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
int count =[tableArray count];
if (ifMore)
{
count= [totalArray count];
}
if (indexPath.row==count-1)
{
return 96;
}
return 56;
}
The cell for row index method is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TableCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[TableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSMutableDictionary *dict=[[NSMutableDictionary alloc]init];
if (ifMore)
{
dict=[totalArray objectAtIndex:indexPath.row];
}
else
{
dict =[tableArray objectAtIndex:indexPath.row];
}
cell.hostLabel.text=[dict objectForKey:#"Event_Name"];
cell.startTime.text=[dict objectForKey:#"Event_Startdate"];
cell.endTime.text= [dict objectForKey:#"Event_Enddate"];
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
int count=[tableArray count];
if(ifMore)
{
count=[totalArray count];
}
if (indexPath.row==count-1)
{
[cell.contentView addSubview:moreBtn];
}
return cell;
}
I am using the code like this. Now my main problem is when I am loading data normally it is good does not make any issue while loading scrolling. Its works perfectly. But when I am clicking on the button in the TableView cell and get data from service and reload data. It works fine. But when I am scrolling it crashes. I am stuck with this.
The crash message is:
2012-12-20 12:22:49.312 IAGDapp[3215:15e03] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 3]'
*** First throw call stack:
(0x4b5052 0x21c7d0a 0x4a1db8 0x2b9e6 0x2b7d2 0xc80a57 0xc80b92 0xc8669e 0x7402db 0x7401af 0x489966 0x489407 0x3ec7c0 0x3ebdb4 0x3ebccb 0x1f6b879 0x1f6b93e 0xc2ba9b 0x28e0 0x2105 0x1)
terminate called throwing an exceptionsharedlibrary apply-load-rules all
kill
Can you help me find the error(s)?
Here when you click on UIButton of cell after here you get another data from services and after you want to load this data in same table so here follow this step..
first when you get data then add this data in your tableArray and totalArray also.
after that reload table.
Its crash because here numberOfRows and other methods are not found this new updated array.
I agree with Paras on this one. I can't see the part where you fetch the data into arrays but if you omit properly reloading data/ getting the data from the wrong array/ setting ifMore variable, your app will crash. First you should check them. If you think nothing is wrong, set a breakpoint inside numberOfRowsInSection and see why it returns 4 where you want to have 4+ cells.
But actually, I dont think you need two different arrays and ifMore variable for that purpose. Have a single array, update it after you fetch the data, reload the table and remove totalArray and all the if statements with ifMore variable. Simplicity may help with stability.
The problem is your loop.
if (indexPath.row==count-1)
{
return 96;
}
Count of your array is 4 and you are trying to load 96 rows in your tableview. Hence, your application is getting crash.
Problem is not scrolling UITableView. Problem is with loading the array in table.
Seems like ifMore variable is not working as you were expecting. Since the error log was index 4 beyond bounds [0 .. 3], it is clear that, the tableview's datasource contains 3 elements only, but the count you returned in numberOfRowsInSection were 4.
As an alternate solution, if you want your More button always as last row in your datasource, why cant you do it using single array. Just return the tableArray.count + 1(for more button) in numberOfRowsInSection method. In cellForRowAtIndexPath, check whether if the index was above tableArray.count, then create button. Same as in heightForRowAtIndex method too.
I have used storyboard in my application in my detail view for UITableView i passed tableview to detail view to be able to delete this row depend on something action but give me below error
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: '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 (4),
plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted)
and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
If you delete the last row in your table, the UITableView code expects there to be 0 rows remaining. It calls your UITableViewDataSource methods to determine how many are left. Since you have a "No data" cell, it returns 1, not 0. So when you delete the last row in your table, try calling -insertRowsAtIndexPaths:withRowAnimation: to insert your "No data" row.
What you need to do is :
// tell the table view you're going to make an update
[tableView beginUpdates];
// update the data object that is supplying data for this table
// ( the object used by tableView:numberOfRowsInSection: )
[dataArray removeObjectAtIndex:indexPath.row];
// tell the table view to delete the row
[tableView deleteRowsAtIndexPaths:indexPath
withRowAnimation:UITableViewRowAnimationRight];
// tell the table view that you're done
[tableView endUpdates];
( According to this link , you should avoid NSInternalInconsistencyException. )
When you add or delete a row you must update your datasource to keep consistency. It seems that your datasource is not updated after deleting the row. To help you it will be useful your datasource methods and the code you use to delete the row.
Your log means that 4-1 = 3, not 4.
You have to take into consideration your deleted rows when returning their count in
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
You should have a variable or array containing your number of rows, e.g.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.cellsCount;
}
// somewhere else
[tableView beginUpdates];
self.cellsCount--; // here is an important line of code
[tableView deleteRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
I'm using the following section of code - it should be simple, but it's causing me a lot of trouble:
NSUInteger index[] = {1,0}; // top row of section one
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath
indexPathWithIndexes:index length:2]] withRowAnimation:UITableViewRowAnimationNone];
The code crashes on the second line, with the error that "The number of rows in section zero is not equal to the number of rows in section zero before the update".
This error is true, I'm changing the number of rows in section zero. But surely this shouldn't effect whether or not I can reload a row in section 1!?
-
Am I misunderstanding how something works, or is something else going wrong somewhere? Any help or ideas are much appreciated :)
I think you also need to update the numberOfRowsInSection.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return rowCount;
}
If you are changing (reloading) the number of rows in section 0 (probably by deleting or adding some rows in it) then -
(NSInteger)tableView:(UITableView
*)tableView numberOfRowsInSection:(NSInteger)section
is called to get the new number of rows. Here you need to return the correct number of rows i.e. one row less, if you deleted a row in section zero.
So basically, if you were returning [someArray count] for number of rows in section 0, you need to update this someArray as well.
I have a tableView that needs to be updated after information has been inserted from another view. If I perform a
[self.tableView reloadData];
The very next time I insert more information in another view and try to reload the table, all the currently visible rows are duplicated.
In other words, when I start up the app I have:
tableView:
Row 1
Row 2
Then I submit some information that will also show up in the table and suddenly I have:
tableView
Row 1
Row 2
Row 3 <- info I just added
Row 1
Row 2
My numberOfRowsInSection implementation looks like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [ItemsController sharedItemsController].count;
}
My cellForRowAtIndexPath implementation looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemsController* controller = [ItemsController sharedItemsController];
NSMutableArray* recentItems = controller.listOfRecentItems;
CustomCell *cell = nil;
NSUInteger row = [indexPath row];
if( row < recentItems.count )
{
Items* item = [recentItems objectAtIndex:row];
if( recentCellData == nil )
recentCellData = [[NSMutableDictionary alloc] initWithCapacity:[indexPath length]];
if( [recentCellData count] > 0 )
cell = [recentCellData objectForKey:[NSString stringWithFormat:#"%d", row]];
if (cell == nil) {
UIViewController * view1 = [[UIViewController alloc] initWithNibName:#"CustomCell" bundle:nil];
cell = (CustomCell*)[view1 view];
[recentCellData setObject:cell forKey:[NSString stringWithFormat:#"%d",row]];
}
// do some other stuff here
}
// Set up the cell
return cell;
}
What's the best way to update the table and avoid duplicating the currently visible rows.
Thank in advance for all the help!
The error isn't in how you're reloading the table, it's in how you're providing data to it. Set a breakpoint in the data source methods and the method that adds new rows to see where you're going wrong.
You'll only end up with five items if tableView:numberOfRowsinSection: returns 5. Thats the simple answer to your question, but I see other problems here. I'm wondering why you have this test: row < recentItems.count. Is that array the same thing as [ItemsController sharedItemsController].count? You really need to be using the same array for both methods.
(Also, it's not a syntax error, but you shouldn't use the property syntax for things that aren't declared as properties. You should write [recentItems count] instead.)
I'm also confused by the code you use to set up the cell. Cells are meant to be reusable. That is, you create one cell, then reconfigure it every time in your implementation of tableView:cellForRowAtIndexPath:. Your code creates a cell for each item in your list. This is very memory-inefficient, and will likely crash your program due to insufficient memory on the iPhone if you keep lots of cells in memory like this.
The recommended approach is to call dequeueReusableCellWithIdentifier:. If that returns nil, then you set up a cell using the initWithFrame:reuseIdentifier: initializer. The table view is very smart, and will only ask you to redraw the cell when it needs you to.
Your recentCellData dictionary looks really shaky to me, too. What if you insert an item after the item with key #"2"? All the items with key #"3" onward will need to be shifted one element to the right to work the way you expect. That's a ton of bookkeeping that seems rather unnecessary to me. If you really needed something like this -- and to be clear, I don't think you do -- why wouldn't you use an NSMutableArray, which is much easier to use?
I added a bit more info above.