I have a couple of NSMutableArrays which i need to clear when refreshing the view. However, when I try to clear them with [array removeAllObjects]; my tableview crashes due to index beyond bounds error. All i do with the refresh, is clear the arrays and call the same function as in viewDidLoad for filling the tableview. [tableView reloadData] doesn't get called until the very last line of the method.
EDIT: It's highly likely that the issue is this: I use a pull to refresh external lib, and when you scroll up and release the table, it bounces back down and thus the UITableView tries to load the next cell, which it cant because the array is cleared, and it's still being loaded.
Answer: removeAllObjects from the arrays, immediately do a self.tableView reloadData and then continue with the rest.
problem might be due to numberOfRowsInSection returning some count and your data source array is empty.
just call [array removeAllObjects] and in numberOfRowsInSection return [array count].
I hope it will resolve your issue. Best of Luck!!!
I delete cells from my table view in the following manner-
NSMutableArray* indexPathsToDelete = [[NSMutableArray alloc] init];
for(unsigned int i = 0; i < [self.array count]; i++)
{
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self.tableView beginUpdates];
[self.array removeAllObjects];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView endUpdates];
[indexPathsToDelete release];
When you refresh your array, first check if it has the object or not & then reinitialize your array and release one.
What i did was
[array removeAllObjects];
then call
[self.tableview reloadData];
Related
Came across this post but did not fix my problem.
iPhone app crashing on [self.tableView endUpdates]
Having a UITableView which loads articles from a newspaper website. First load works
as expected but when I use an UIRefreshControl to fetch the articles again my app crashes
when (animating) inserting rows.
Error:
Code:
- (void)insertRowsWithAnimation
{
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger i = self.latestArticlesArray.count - self.latestArticlesArray.count;
for (NSDictionary *dict in self.latestArticlesArray) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
i++;
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationMiddle];
[self.tableView endUpdates];
}
- (void)fetchEntries
{
UIView *currentTitleView = [[self navigationItem] titleView];
UIActivityIndicatorView *aiView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[[self navigationItem] setTitleView:aiView];
[aiView startAnimating];
void (^completionBlock) (NSArray *array, NSError *err) = ^(NSArray *array, NSError *err) {
if (!err) {
[[self navigationItem] setTitleView:currentTitleView];
self.latestArticlesArray = [NSArray array];
self.latestArticlesArray = array;
[self insertRowsWithAnimation];
}
};
[[Store sharedStore] fetchArticlesWithCompletion:completionBlock];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.latestArticlesArray.count;
}
If you want to see more methods please let me know. Hope you can help. From what I've learned so far I think the number of posts have changed therefore the table expects another amount of articles to show?
The line
NSInteger i = self.latestArticlesArray.count - self.latestArticlesArray.count;
sets i to zero, therefore insertRowsAtIndexPaths is called with an empty array.
I assume that your intention was to call insertRowsAtIndexPaths with the row numbers of the newly added rows, but then you have to remember the old data before replacing the array in
self.latestArticlesArray = array;
But note that since you replace the entire array, you can as well call
[self.tableView reloadData];
instead of beginUpdates/insertRowsAtIndexPaths/endUpdates.
Update: My first analysis is wrong (and wattson12's is correct). As you said in the comments, you need just a simple animation that removes all previous rows and inserts the new rows after a fetch. This can be done like this:
- (void)replaceArticlesWithAnimation:(NSArray *)articles
{
NSUInteger oldCount = [self.latestArticlesArray count];
self.latestArticlesArray = articles;
NSUInteger newCount = [self.latestArticlesArray count];
[self.tableView beginUpdates];
for (NSUInteger i = 0; i < oldCount; i++) {
[self.tableView deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
for (NSUInteger i = 0; i < newCount; i++) {
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
[self.tableView endUpdates];
}
and in fetchEntries you call
if (!err) {
[[self navigationItem] setTitleView:currentTitleView];
[self replaceArticlesWithAnimation:array];
}
The reason it loads the first time is because you are inserting a number of rows each time, so on the first run the number of rows changes from 0 to the count in array (in the completion block), and you call insertRows with a number of index paths equal to the count of the array.
The 2nd time you call it you are inserting new rows, but you are not updating the count to reflect the new sum. You should be adding your existing array to the one returned in the completion block, and returning the count of that combined array in numberOfRowsInSection
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
});
Below is the function that i am using to get and parse different pages of the feed.
- (void)getFeed:(NSInteger) pageNumber
{
collectedData = [[NSMutableData alloc] init];
NSString *urlStr =[NSString stringWithFormat:#"http://sitename.com/feed/?paged=%d", pageNumber];
NSURL *url = [NSURL URLWithString:urlStr];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
}
When user scrolls down to the bottom of tableview, i use the below function to access the second page of feed and so on by incrementing the counter:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height)
{
counter ++;
[self getFeed:counter];
}
}
The issue is when instead of loading it below the already fetched items, it reloads the tableview to show the new page items and old ones disappear. I want to load new page items below the existing ones to have infinite scroll. How can i do that?
It feels bad to answer your own question, but taking help from another forum, i was able to figure out the answer.
The tableview controller was not in sync with the mutable array, so i created another and append both arrays.
Step1: I created a property
#property (nonatomic, readonly) NSMutableArray *moreItems;
Step2: Initialised the array in ViewDidLoad before calling fetchEntries
moreItems = [[NSMutableArray alloc] init];
Step3: In connectionDidFinishLoading, i appended new array 'moreItems' to the previous array 'items'
[moreItems addObjectsFromArray:[channel items]];
Step4: Changed the data source in numberOfRowsInSection from
return [[channel items] count];
to new array
return [moreItems count];
and then in cellForRowAtIndexPath, used the new array
RSSItem *item = [moreItems objectAtIndex:[indexPath row]];
You should use insertRowsAtIndexPaths: withRowAnimation: method of UITableView.
One thing you must take care of is that when you use this method, number rows retuned by tableView:numberOfRowsInSection should be equal to number_of_existing_rows + number_of_newly_inserted_rows
After you finish retrieving the new feed for next page get the objects in an array.
Add these objects from this array to your data source array.
Now insert rows to the table as follows:
[yourTable beginUpdates];
[yourTable insertRowsAtIndexPaths:indexPathsToInsert
withRowAnimation:UITableViewRowAnimationBottom];
[yourTable endUpdates];
where "indexPathsToInsert" is array of NSIndexPath objects starting from the last row in your table and containg indexPaths for your new objects.
Hope this helps.
Hi,
Create a for loop, with the starting index is (number of objects in datasource) and number of objects you want to add (count),create the array of indexpaths and call insertRowsAtIndexpaths:withRowAnimation. I hope the code helps you.
NSInteger statingIndex = [self.tableViewDatasource count];
NSInteger noOfObjects = //Get the no.of objects count from getFeed method.
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for (NSInteger index = statingIndex; index < statingIndex+noOfObjects; index++) {
[_objects addObject:]; // add the object from getFeed method.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[indexPaths addObject:indexPath];
[indexPath release];
}
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
Whenever your array gets a new object added you call this
-(void)insertRowInTableView
{
int row = [YourDataSourceArray count];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[YourTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
}
Here YourDataSourceArray is the array from which you are setting your tableview row count/data.
YourTableView is your tableView object name.
This may be because when ever you are getting your data from the webservice you are adding it in the Array you are using to populate the table, by this you will have only new data, what you have to do is you have to append the new data to the array(Make it Mutable Array and append new data) and just use the following line
[your_Table reloadData];
Hope this will help you.
Instead of using scrolling down to load, better use the last row of tableview as "Load More Items" and when the user selected that row, get the new feed and reload the tableview.
And also I have one doubt in your code, everytime in getfeed method, you are creating array
collectedData. I doubt you missing of appending this data to table datasource data,which can cause to show you only recent feed data in tableview. Please check
Update for code: Following on Matthew's answer I tried correcting my code to be more correct. Now the code does delete the cell but also crashes and gives an error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'
The code below is from an action called checkboxTapped which is in my CustomCell code. Once action is fired it gives the error. I figured out that my indexPath is equal to NULL, and thats most likely the issue. But I don't know how to fix it.
[self.textLabel setTextColor:[UIColor grayColor]];
[self.detailTextLabel setTextColor:[UIColor grayColor]];
parent = [[ViewController alloc] init];
db = [[DataObject alloc] init];
NSIndexPath *indexPath = [[parent tableView] indexPathForSelectedRow];
[[parent array] removeObjectAtIndex:[indexPath row]];
[db deleteTaskAtIndex:[indexPath row]];
[[parent tableView] deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[db release];
[parent release];
Old: I looked through my code and I printed my array I was using and it appears to be fine, yet this error still persists.
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM removeObjectAtIndex:]: index 1 beyond bounds [0 .. 0]'
My guess was that it had something to do with my indexPath but it doesn't make much different how much I change it.
-(void)checkboxTapped:(id)sender
{
[sender setSelected:YES];
[self.textLabel setTextColor:[UIColor grayColor]];
[self.detailTextLabel setTextColor:[UIColor grayColor]];
parent = [[ViewController alloc] init];
UITableView *tableView = parent.tableView;
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:parent.array];
[parent release];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[array count] inSection:1];
[array removeObjectAtIndex:[indexPath row]];
[db deleteTaskAtIndex:[indexPath row]];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[array release];
[tableView endUpdates];
[tableView reloadData];
}
In your code [indexPath row] is going to return the value of [array count]. That's unlikely to be what you want. If your array has zero objects in it, you are going to attempt to remove the object at index 0. But there will be no objects and you'll get an error. If your array has 1 object in it, you're going to attempt to remove the object at index 1. Again, that will fail, because there is no object at index 1, just one object at index 0.
If you want to remove the last object in an array you need to use an index that is count-1. You may also need to check to see if the array is empty, if that case can occur.
Updated in response to follow up in comment
You don't want to do anything indexPathWithIndex. As a first step, try modifying your code along the following lines:
-(void)checkboxTapped:(id)sender
{
[sender setSelected:YES];
[self.textLabel setTextColor:[UIColor grayColor]];
[self.detailTextLabel setTextColor:[UIColor grayColor]];
parent = [[ViewController alloc] init]; // looks very odd - is an instance of this viewController active when the checkBox is tapped? If so, you don't want to create a new one, you want to access the existing one
UITableView *tableView = parent.tableView;
[parent release]; // this looks very dicey - when you release the parent, won't it release the tableView too?!
int lastRow = [array count] - 1;
if (lastRow == 0)
{
return; // bail if there are no rows in the table
}
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:parent.array];
[array removeObjectAtIndex: lastRow]; // not clear this will do anything as the reference to array is discarded later
[db deleteTaskAtIndex: lastRow];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow: lastRow inSection:1];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[array release];
// [tableView endUpdates]; // there's no matching beginUpdates and you're only do one change operation anyway - leave this out
// [tableView reloadData]; // if you leave this line in, you won't see the delete animation - if you just want to delete one row, you wouldn't normally use reloadData, at least not if you want the animation
}
All this said, it looks as if there are other things going on here.
What's happening with array? You create this, remove an item from it and the discard the pointer to it. Is that what you really want to do. A more common pattern would be to get the pointer to the array from the other object and remove the item at the end of it here.
It's not clear from your code how you are updating the table's data source. When using deleteRowsAtIndexPaths:withRownAnimation you need to make sure the table's data source will return one row less than it did last time it was asked with tableView:numberOfRowsInSection:. From your code it's not clear how the tableView dataSource is going to know there's one less item, unless, perhaps, it's looking at whatever it is that db is pointing to in order to find this out.
More fundamentally, with a typical design pattern the tableView is going to be released when you release the parent view, so whatever it points to after `[parent release]' is going to do something undefined and is likely to crash at least some of the time.
I have a UITableView. I'm population it from a NSDictionary with arrays for each set of items on the table: labels, footers, Headers, UIViews, etc.
In section 0, I want a row #2 appear when a switch in row #1 is switched to on.
What I have done and it works is, in numberOfRowsInSection I added this code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (interruptor.isOn==NO && section==0) {
return [[[infoTableContentArray objectAtIndex: section] objectForKey:kLabelKey] count]-1;
}else{
return [[[infoTableContentArray objectAtIndex: section] objectForKey:kLabelKey] count];
}
}
and the action linked to the switch (interruptor) is:
-(IBAction)accioInterruptor:(id)sender{
[infoAndSettingsTable reloadData];
}
so when the switch is switched, the table reloads and the cell appears or disappears.
it actually works, but there is no animation, which makes it, mhh... well, you know.
I've tried to implement the reloadRowsAtIndexPaths:withRowAnimation, adding it to the code called by the switch:
-(IBAction)accioInterruptor:(id)sender{
[infoAndSettingsTable beginUpdates];
[infoAndSettingsTable reloadRowsAtIndexPaths:[[infoTableContentArray objectAtIndex: 0] objectForKey:kLabelKey] withRowAnimation:UITableViewRowAnimationBottom];
[infoAndSettingsTable endUpdates];
}
But, it dowsn't work. It crashed on the line [infoAndSettingsTable endsUpdates];
BTW, in all the cases the following:
[[infoTableContentArray objectAtIndex: 0]
is the array which contains the labels for that section.
Am I doing it right or I'm Epic-Failing alltogether?
Thanks in advance!
simple way to do this......
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
insertIndexPaths is an array of NSIndexPaths to be inserted to your table.
deleteIndexPaths is a array of NSIndexPaths to be deleted from your table.
Example array format for index paths :
NSArray *insertIndexPaths = [[NSArray alloc] initWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:1 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
nil];
got it from this question
the argument to reloadRowsAtIndexPaths: should be an array of NSIndexPath objects identifying the rows you want to reload, not the labels for that section. Also, looks like you want to reload a section so I would try the following:
-(IBAction)accioInterruptor:(id)sender {
[self reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationBottom];
}
Why don't you just use UITableView's insertRowsAtIndexPaths:withRowAnimation:? It has been built exactly for this purpose, the UITableView class reference has the exact description and usage examples.
Apart from being cleaner it is also more performant since you don't have to reload the entire table (only really matters if you have lots of cells in it though)
I am getting this error while I am trying to load the data into my table view.
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 (73) must be equal to the number of rows contained in that section before the update (71), plus or minus the number of rows inserted or deleted from that section (3 inserted, 0 deleted).
What could be wrong?
Thanks
EDIT :
I am initializing the array on ViewWillAppear and adding new objects to the same array on Tableview's didSelectRowAtIndexPath method
Here is the code On viewWillAppear :
cellTextArray = [[NSMutableArray alloc] init];
[cellTextArray addObjectsFromArray:newPosts];
Here is the code which modifies the array on didSelectRowAtIndexPath :
[cellTextArray addObjectsFromArray:newPosts];
NSMutableArray *insertIndexPaths = [NSMutableArray array];
for (NSUInteger item = count; item < count + newCount; item++) {
[insertIndexPaths addObject:[NSIndexPath indexPathForRow:item
inSection:0]];
}
[self.table beginUpdates];
[self.table insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation:UITableViewRowAnimationFade];
[self.table endUpdates];
[self.table scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionNone
animated:YES];
NSIndexPath *selected = [self.table indexPathForSelectedRow];
if (selected) {
[self.table deselectRowAtIndexPath:selected animated:YES];
}
Here newPosts is an array which has the values that are added to cellTextArray on
didSelectRowAtIndexPath method and viewWillAppear method.
If you have updated the array of data after initialization then you can call this method [yourTable reloadData].
And, it be better if you post the codes here. Then may be some one can help you quickly.
I think, the problem is with the statement in the didSelectRowAtIndexPath method.
You have added the following statement
[self.table insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationFade];
This statement adds the new rows to the table. but the number of rows in the datasource array for that section is different with the number of rows in that section of that table.
So the App is terminated.
Please try by removing the above statement and add required data to the dataSource Array at required indexes and reload the table
Regards,
Satya