I'm having the following issue raised by Crashlytics :
[__NSArrayM objectAtIndexedSubscript:]: index 5 beyond bounds for empty array
-TopicListViewController tableView:cellForRowAtIndexPath:]
While accessing the dataSource with indexPath.row.
We have some asynchronous data update updating the datasource, and that variable is nonatomic.
Would it be possible that cellForRowAtIndexPath is called while the dataSource is being updated? Hence causing to access an index that doesn't exist anymore?
Can it be because the variable is nonatomic?
Here's the relevant code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row > [self.tableData count] - 1 || ![self.tableData isValidArray]) {
return nil; //Some protection to prevent this issue...
}
TopicCell * cell = (TopicCell *)[tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
cell.delegate = self;
NSDictionary * data = nil;
if (self.we_isSearching) {
data = self.we_searchResult[indexPath.row];
} else {
data = [self.tableData objectAtIndex:indexPath.row]; //Crashes here
}
"index 5 beyond bounds for empty array" simply states that either you didn't initialise your array or the range of the value you're accessing is out of the range of the array. You are trying to access index 5 in an empty/having less element array, or non initialised array that's why it is giving you "outOfBounds" in cellForRowAtIndexPath.
Would it be possible that cellForRowAtIndexPath is called while the dataSource is being updated?
Yes, cellForRowAtIndexPath will always be called when you're going to see a new tableview cell for example when you're scrolling the tableview Or in case you've added some kind of notification added to your datasource or by reloading the tableview.
You can put a break point at cellForRowAtIndexPath and check the stack trace maybe you get something that causes the tableview to reload.
Try to count from self.tableData in return of numberOfRowsInSection methods. Like
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.tableData count];
}
pass array count in numberOfRowsInSection of tableView method.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return array.count;
}
Your condition
if (indexPath.row > [self.tableData count] - 1 || ![self.tableData isValidArray])
is wrong. If there is 5 elements, the last indexPath.row will be index 4 so condition with real values will be:
if (4 > 5 - 1) --> if 4 > 4
So the valid condition is:
if (indexPath.row >= [self.tableData count] - 1)
But with correct condition you will have crash on:
return nil
Because obvisously your data source is different than table data source. Your model data source should be always same as table data source.
Related
please suggest me how to display data like tree view.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return marrSearchResult.count;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
int iCount=0;
if (section==0) {
iCount =(int)[marrSearchResult count];
}else if(section==1){
iCount =(int)[[[[marrSearchResult objectAtIndex:section] valueForKey:#"data"] valueForKey:#"result"] count]+1;
}else{
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *simpleTableIdentifier = #"MyTravelProfileTblCELL";
if (indexPath.section == 0) {
}else{
}
}
see below image desplay like this
Follow these steps :
Create custom tableview cell for section header (Tourist attractions, Government/Community). Lets call it PlaceTypeCell
Create another custom tableview cell for rows (Museums, Hospitals... ). Lets call it PlaceNameCell.
Return the section count as the count of main array (you already did it)
Return the row count as the count of subarray at the index of section in the main array (also you did it)
Now in the cellForRowAtIndexPath, get the sub array from main based on section number (indexPath.section). Now get the required row item(Place name and details) from sub array using row number (indexPath.row). Using the data in that row item populate the table cell.
Now in the viewForHeaderInSection get the place type details from the main array using section number and populate the PlaceTypeCell and return as section header. Have a look at Best way to create custom UITableview section header in storyboard
Set the height for section header using heightForHeaderInSection
Hope it helps.
Edit : If you can show the format of marrSearchResult, then it will be easy to help.
Create Input array in Format:
NSArray *tourismSection = #[#"Museums", #"Aquariums"];
NSArray *govSection = #[#"Hopsital"];
NSDictionary *data = #{
#"tourism" : tourismSection,
#"gov" : govSection
};
NSArray *inputs = #[#"tourism",#"gov" ];
in numberOfSectionsInTableView
return inputs.count;
in numberOfRowsInSection:
return [data valueForKey:inputs[indexPath.section]].count;
I'm trying to make a UITableView present a determined number of rows for a section, but even when I verify that its data source is returning x number of rows for numberOfRowsInSection, the table view shows x-1.
The exception to this unexpected behavior is if the numberOfRowsInSection is less than 3.
I've even put a breakpoint in cellForRowAtIndexPath and I confirmed it's being called for the row that is not appearing.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == SectionNumber_One) {
return 6;
} else {
return self.numberOfProjectRows; // This returns x, but x-1 rows are being shown
}
}
For example, if self.numberOfProjectRows is 5, only 4 rows are shown for the second section.
If I increase it manually to 6, it shows 5 rows but the data that should be in the 5th position, isn't there.
It doesn't seem to be related to screen size as I tested it on an iPad with same results.
Why is this happening? Is there some other possible modifier of the number of rows in a section?
I'm attaching an screenshot if it's of any help.
EDIT - Here are my delegate methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Cell with reuse identifier setup
if (section == SectionNumber_One) {
// cell setup for section one that's showing up ok
} else if (section == SectionNumber_Two) {
UITextField *projectField = cell.projectTextField;
if ([self.userProjectKeys count] > row) {
projectField.text = self.availableProjects[self.userProjectKeys[row]];
}
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Hide the password row for existing users
if (indexPath.row == FieldTag_Password && ![self.user.key vol_isStringEmpty]) {
return 0.0f;
} else {
return UITableViewAutomaticDimension;
}
}
The problem is probably not in your Datasource methods, but your Delegate methods, tableView(_:heightForRowAt:).
If you do not return a correct height for the cell, it won't show up.
It doesn't matter if you write 1000 cells in your datasource. If you don't return the height, they wont show up.
You are not comforming to the MVC pattern in implementing the table. You must return the count of the tableView's datasource, not variables of it.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == SectionNumber_One) {
return 6;
} else {
return [_displayingArray count]; // <-- here
}
}
If you want different items for each sections, then declare different datasource (ie array) for each of them.
EDIT: Even returning constant 6 is dangerous in the other section - you ought to add items to another fixed array and return that array's count in this delegate.
I'm new to iOS dev so this might be a simple question to you but I check the apple dev guide just could not find it.
I have many UITableViews using one dataSourceDelegate,this is how I create them:
- (void)createSomeTableview:(NSInteger)numberOfTableViews{
tableViewsArray = [[NSMutableArray alloc] initWithCapacity:numberOfTableViews];
for (int i = 0; i < numberOfTableViews; i++) {
UITableView *itemTableView = [[UITableView alloc] initWithFrame:CGRectMake(90, 0, 235, self.view.bounds.size.height) style:UITableViewStylePlain];
[itemTableView setDataSource:self];
[itemTableView setDelegate:self];
[itemTableView registerClass:[KKYItemListCell class] forCellReuseIdentifier:[NSString stringWithFormat:#"%ld", (long)currentSection]];
[tableViewsArray addObject:itemTableView];
[[self view] addSubview:itemTableView];
}
}
I call createSomeTableview when I finish got my data back from my server,then I use a cycle to reload the data of each tableView (I call this in a block of AFNetwoking POST after got the response successfully):
for (int i = 0; i < sectionNum;i++){
currentSection = i;
[[tableViewsArray objectAtIndex:i] reloadData];
}
This is where the disaster came!(T T)There is always a null cell return from the reloading process so I track the reloading process and found a strange thing:
Once I call the method reloadData,the method numberOfRowsInSection is called (I have just one section for each table),after this then go back to call the reloadData of the next Tableview in tableViewsArray.(I used to think the reloadData will call cellForRowAtIndexPath next).
After all the views in tableViewsArray calling numberOfRowsInSection,the method cellForRowAtIndexPath had been called by the last view in tableViewsArray.Well that's fine,it starts to build the data for my tableview~,I do this to specify my tableViews in array:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
.....
if (tableView == sectionTableView) {
...
} else if (tableView == [tableViewsArray objectAtIndex:(long)currentSection]) {
...//data handle
if (itemsNum == 0) { //itemsNum is the count of current table's items
currentSection--; //if loaded end then go to the next tableView
return cell;
}
itemsNum--;
return cell;
}
return cell;
}
But something wrong with it!!! I got 18 items in the current data from server (itemsNum = 18),and I did get 18 return from numberOfRowsInSection.But when the indexPath.row == 6,the tableView's index is currentSection - 1,which means it's no longer the current tableView (actually it was the next tableView)is calling the method `cellForRowAtIndexPath'.
So (tableView == [tableViewsArray objectAtIndex:(long)currentSection]) is false,method return a nil cell,then crashed.
I'm wondering where does the method 'tableView:cellForRowAtIndexPath' get the para row in indexPath?Isn't the return of 'numberOfRowsInSection'?(not seemed like in my program).
in method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
usually you use an array to set
[cell.textLabel setTex:#"row"];
but if I want to jump a row?
at example at indexpath.row I don't want to have this cell in my tableview, is possible?
Try this: Incorporate the tableView datasource method for row height.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == NUMBER_TO_AVOID) {
return 0.0f;
}
return 44.0f; //standard cell height
}
I have done this in several similar situations. What you'll do is create a second array "activeItems". Or something like that. Iterate through your main data array and build the active array with the valid items. Than have your data source reference this array instead. This gives you an array that is accurately indexed to your table.
You can put a condition in cellForRowAtIndexPath. Try this:
data = [array objectAtIndex:indexPath.row];
If (data != nil){
[cell.textLabel setText:data];
}
else{
// code that handles data if null
}
I´m quite new to iOS development and I´m having a terrible time by trying something that should be easy; to add an extra row in a TableView everytime the user clicks on one of the existing rows. There is no real purpose on that action, I´m just wanting to understand the behaviour of TableView.
So I did the following:
I used a Split View-based template and changed the number of rows to 30 in the RootViewController.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 30;
}
The method tableView:didSelectRowAtIndexPath looks in the following manner:
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
*/
NSMutableArray* paths = [[NSMutableArray alloc] init];
NSIndexPath *indice = [NSIndexPath indexPathForRow:30 inSection:0];
[paths addObject:indice];
detailViewController.detailItem = [NSString stringWithFormat:#"Second Story Element %d with all its information and bla bla bla", indexPath.row];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
When I execute the program and click on one of the elements, I receive the following 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 (30) must be equal to the number of rows contained in that section before the update (30), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted).'
I did not change any other part of the code that the template provides.
I read quite extensively the documentation from Apple and the responses to the following questions:
Add a row dynamically in TableView of iphone
and
how to properly use insertRowsAtIndexPaths?
The second question seems to address the same problem, but I´m not capable to understand what is happening. What do they mean with dataSource? The response that I understand better says the following:
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
What does this update of the data source implies?
Sample code would be HIGHLY appreciated, because I´m totally frustrated.
By the way, all that I´m trying has nothing to do with entering the editing mode, has it?
You need to keep the count returned by tableView:numberOfRowsInSection: in sync!
So when you have 30 rows and then tell the tableview to insert a new row you need to make sure tableView:numberOfRowsInSection: will now return 31.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
return self.rowCount;
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.rowCount++;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
In practice you would probably use an array to track your rows return [self.rows count]; etc
The answer is quite simple. When you want to modify a table view you need to perform two simple steps:
Deal with the model
Deal with table animation
You already perform the second step. But you have missed the first one. Usually when you deal with a table you pass it a data source. In other words some data to display within it.
A simple example is using a NSMutableArray (it's dynamic as the name suggests) that contains dummy data.
For example, create a property like the following in .h
#property (nonatomic, strong) NSMutableArray* myDataSource;
and in .m synthesize it as:
#synthesize myDataSource;
Now, you can alloc-init that array and populate it as the following (for example in viewDidLoad method of your controller).
self.myDataSource = [[NSMutableArray alloc] init];
[self.myDataSource addObject:#"First"];
[self.myDataSource addObject:#"Second"];
Then, instead of hardcoding the number of rows you will display (30 in your case), you can do the following:
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
return [self.myDataSource count];
}
Now, in you didSelectRowAtIndexPath delegate you can add a third element.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.myDataSource addObject:#"Third"];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
It looks like one big problem is with tableView:numberOfRowsInSection:. You need to return the correct number of rows in that method.
To do that, it's usually best to maintain an NSArray or NSMutableArray of items for the table view so in that function, you can say: return [arrayOfValues count];. Keep the array as a property of your view controller class so that it's readily accessible in all methods.
The array can also be used in cellForRowAtIndexPath:. If you have an array of NSString, you can say cell.text = [arrayOfValues objectAtRow:indexPath.row];.
Then, when you want to add an item to the table view, you can just add it to the array and reload the table, e.g. [tableView reloadData];.
Try implementing this concept and let me know how it goes.
You can Also do that for dayanamic table cell
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayStationStore count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndentyfire;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentyfire];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentyfire];
}
cell.textLabel.text = [arrayStationStore objectAtIndex:indexPath.row];
return cell;
}
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Check if current row is selected
BOOL isSelected = NO;
if([tblStationName cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
isSelected = YES;
}
if(isSelected)
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
[arrayReplace removeObject:indexPath];
NSLog(#"array replace remove is %# ",arrayReplace);
}
else
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[arrayReplace addObject:indexPath];
NSLog(#"array replace add is %# ",arrayReplace);
}
return indexPath;
}