Simple question,
I loop thru my table view cells and add the objects to an array:
NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger j = 0; j < [self.ammoTable numberOfSections]; ++j) // loop thru sections
{
for (NSInteger i = 0; i < [self.ammoTable numberOfRowsInSection:j]; ++i)//in each section loop thru the cells
{
[cells addObject:[self.ammoTable cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]];
}
}
This works PERFECT, as long as the table view has only 7 or less cells, if i add 8 or more, the app crashes with this log to the console:
'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Any help is greatly appreciated!
The cellForRowAtIndexPath: method of UITableView returns nil if the index path refers to a cell that is currently not visible, therefore
[cells addObject:[self.ammoTable cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]];
crashes in that case.
it lead to crash because you have only seven rows at the screen, when it all added to array after that it returns nil. for solving what you can do is create a method which take input as indexpath and returns cell object without using reuse cell.
The magic is in cellForRowAtIndexPath:. For certain values this is returning nil. Check this method and fix it.
Related
TL:DR version: I used NSZombieEnabled to find source of EXC_BAD_ACCESS error and saw a library has 1 more release than retains. Can I assume that this library is causing the crash or can that release be associated with a retain from another library?
I am having some problems in my app with a UITableViewCell subclass instance getting messages after its retain count reaches 0. I ran the app with NSZombies and am currently trying to pair the retain/release calls to find where exactly is the error originating from. I noticed that there are only 2 retains and 3 releases with "Responsible Library" set to QuartzCore. Does it mean that that extra release call is the one that causes my app to crash? Or is it possible that a release has associated retain in another library?
Additional info:
My section headers are tappable, when one is selected, the row for this section is inserted into table view and any previously visible row is deleted. In other words, there can be only 1 section at a time that has 1 row, all other sections must have 0 rows.
The release/retain calls from QuartzCore that I paired are:
CALayer layoutSublayers (retains)
CA::Layer::layout_if_needed(CA::Transaction*) (releases)
The release without a pair is:
CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*)
The exact line where I get the crash is the endUpdates line in:
- (void)sectionHeaderView:(SectionHeaderView *)sectionHeaderView sectionOpened:(NSInteger)sectionOpened {
SectionInfo *sectionInfo = [self.sectionInfoArray objectAtIndex:sectionOpened];
sectionInfo.open = YES;
NSMutableArray *indexPathsToInsert = [[NSMutableArray alloc] init];
[indexPathsToInsert addObject:[NSIndexPath indexPathForRow:0 inSection:sectionOpened]];
/*
Create an array containing the index paths of the rows to delete: These correspond to the rows for each quotation in the previously-open section, if there was one.
*/
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
NSInteger previousOpenSectionIndex = self.openSectionIndex;
if (previousOpenSectionIndex != NSNotFound) {
SectionInfo *previousOpenSection = [self.sectionInfoArray objectAtIndex:previousOpenSectionIndex];
previousOpenSection.open = NO;
previousOpenSection.category.model = nil;
[previousOpenSection.headerView toggleOpenWithUserAction:NO];
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:0 inSection:previousOpenSectionIndex]];
}
// Style the animation so that there's a smooth flow in either direction.
UITableViewRowAnimation insertAnimation;
UITableViewRowAnimation deleteAnimation;
if (previousOpenSectionIndex == NSNotFound || sectionOpened < previousOpenSectionIndex) {
insertAnimation = UITableViewRowAnimationTop;
deleteAnimation = UITableViewRowAnimationBottom;
}
else {
insertAnimation = UITableViewRowAnimationBottom;
deleteAnimation = UITableViewRowAnimationTop;
}
NSIndexPath *indexToDelete = [indexPathsToDelete firstObject], *indexToInsert = [indexPathsToInsert firstObject];
if (indexToDelete == nil) {
NSLog(#"no row to delete");
}
else {
NSLog(#"deleting row %d section %d", [indexToDelete row], [indexToDelete section]);
}
NSLog(#"inserting row %d section %d", [indexToInsert row], [indexToInsert section]);
// Apply the updates.
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:insertAnimation];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:deleteAnimation];
[self.tableView endUpdates]; // this is the crash.
self.openSectionIndex = sectionOpened;
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:sectionOpened] atScrollPosition:UITableViewScrollPositionNone animated:YES];
}
The error happens on iOS7.
You can get a EXC_BAD_ACCESS at the endUpdates line if the index paths in the arrays are invalid (e.g. a section number of -1). You should NSLog your indexPathsToInsert and indexPathsToDelete and make sure they have valid values in them.
I have already found a fix to this problem. It seems that one of the subviews of the table cell was calling becomeFirstResponder after an asynchronous request has ended. Of course if the cell was freed already by that moment, the crash happened. I just assumed at first that the problem is because of unbalance in retains and releases but looks like that wasn't it.
And to answer the question in title: When running with NSZombieEnabled Instruments app already pair some retain/releases when it can assume they are connected. On one of the runs of my app, Instruments joined paired that release coming from QuartzCore with a retain coming from UIKit. Even though I can't be 100% sure that it's right, I assume that "Responsible library" tags being identical is not obligatory.
TL:DR - I'm pretty sure that retain can be paired with release coming from another library.
I'm testing my application on different devices. The app works fine on my iPhone 5, however, when I test it on my iPod touch 4g, I get this eror:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** [__NSArrayMinsertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(0x33f252a3 0x3bba397f 0x33e6f8d9 0x51e25 0x35e180c5 0x35e1814d 0x35e180c5 0x35e18077 0x35e18055 0x35e1790b 0x35e17e01 0x35d405f1 0x35d2d801 0x35d2d11b 0x37a1f5a3 0x37a1f1d3 0x33efa173 0x33efa117 0x33ef8f99 0x33e6bebd 0x33e6bd49 0x37a1e2eb 0x35d81301 0x4fc8d 0x3bfdab20)
libc++abi.dylib: terminate called throwing an exception
The code that's crashing is this one:
/********/
NSMutableArray *titulosCampoTextArray = [[NSMutableArray alloc] init];
for (int i = 0; i < campos.count; i++) {
SignupCell *cell = (SignupCell*)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[titulosCampoTextArray addObject:cell.textField.text];
}
/********/
I'm using custom prototype cells (SignupCell.h), on a UITableView inside a UIView.
Can't find the error, or how to fix it.
EDIT: Both devices are running iOS 6.1.3, and I'm using Xcode 4.6.1.
UITableView's -cellForRowAtIndexPath: method returns nil if the cell isn't visible on screen when you call it.
My guess is that on the iPhone 5 your table can display all rows at once, while on the shorter iPod touch, the last one or two cells aren't visible and -cellForRowAtIndexPath: thus returns nil, which makes your app crash.
You shouldn't rely on -cellForRowAtIndexPath to request model data! Your view controller is responsible for the data, not the table view.
Use the same way to access the model data like you do for setting the table view cells' text in the -tableView:cellForRowAtIndexPath: data source method of your view controller.
Just do this:
NSMutableArray *titulosCampoTextArray = [[NSMutableArray alloc] init];
for (int i = 0; i < campos.count; i++) {
SignupCell *cell = (SignupCell*)[self.tableView
cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
if (cell.textField.text) {
[titulosCampoTextArray addObject:cell.textField.text];
}
}
I don't know why you apparently can add nil on an iPhone 5 but not on an iPod. It may be that for some other reason you just never happen to have a nil value for cell.textField.text when your app is running on the iPhone 5.
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 would like to simulate the SMS Bubbles of the iPhone for my own app. I found some nice code overhere (FYI): http://vimeo.com/8718829 . It is a restyled UITableView. Exactly what a wanted.
Here is the problem:
- The Tableview is filled with an array of messages
- It needs to be a NSMutable array because you want to add messages on the fly.
- When there are no messages yet, the message-array is empty.
- But counting an empty NSMutableArray causes an exeception, the app crashes. (you need the count for scrolling).
So what is a nice solution for that? I now pre fill the array with "". But that is very ugly. You see a mini bubble on the screen.
Can you hide cells? In the example on the video, there are already 2 messages so the problem does no occur.
Any suggestion is welcome. Tnx
Christian
Actually counting an empty array does not raise any exception. I think the problem is here:
- (void)add {
if(![field.text isEqualToString:#""])
{
[messages addObject:field.text];
[tbl reloadData];
NSUInteger index = [messages count] - 1;
[tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
field.text = #"";
}
}
As the "-1" index cannot exist. You can edit that line to
NSUInteger index = MAX(0, [messages count] - 1);
And it should work.
You can count an empty array (I assume you mean [arrayName count]) as long as its alloc'd so make sure its initialized somewhere earlier.
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.