This code causes the app to crash! I'm looping through a dictionary and everything logs out fine. It works when i write -1 in the for loop but not otherwise,
-(void)updateProjectTimes {
for(int i = 0; i < [projectsTable numberOfRowsInSection:0]-1; i++) {
NSString *currentValue = [[[projects objectForKey:#"activeProjects"] objectAtIndex:i] objectForKey:#"timepassed"];
NSString *finishValue = [[[projects objectForKey:#"activeProjects"] objectAtIndex:i] objectForKey:#"timeleft"];
if([currentValue intValue] < [finishValue intValue]) {
[[[projects objectForKey:#"activeProjects"] objectAtIndex:i] setObject:[NSString stringWithFormat:#"%d", ([currentValue intValue] + 1)] forKey:#"timepassed"];
} else {
[(NSMutableArray *)[projects objectForKey:#"activeProjects"] removeObjectAtIndex:i];
if([[projects objectForKey:#"activeProjects"] count] == 0) {
[projectTimer invalidate];
projectTimer = nil;
}
}
//[projectsTable reloadData]; works if i put it here!
}
[projectsTable reloadData]; //<- But not here!! :(
}
I assume you are wokring with tableView. Apparently you are modifing the data source of your tableView. When you remove an object from the data source, you have to adjust your tableView as well. Meaning either call reloadData, or call [tableView deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation].
The reason your app doesn't crash if you put -1, could be -- perhaps -- because there is only on item that matches the [currentValue intValue] < [finishValue intValue] condition, so that if you go through [projectsTable numberOfRowsInSection:0]-1, after removing that object, numberOfRowsInSection matches the count of projectsTable.
But it is good only for one cycle. When the next cycle happens, in the if...loop, you app crashes again, unless you include the [projectsTable reloadData] in the same if...loop.
While the reloadData method works just fine, but if you are simply removing a row, or adding a row to your table by adding or removing objects, its better to use deleteRowsAtIndexPaths or insertRowsAtIndexPaths methods. There will be less overhead and work for your app, and would make it smoother and faster.
The bottom line, to make your code work, right after [(NSMutableArray *)[projects objectForKey:#"activeProjects"] removeObjectAtIndex:i]; remove the corresponding object from your tableView by calling deleteRowsAtIndexPaths.
Alternatively, you can also use beginUpdates and endUpdates. For a complete reference, refer to http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html
Hope it helps.
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.
Here is my code below:
- (UITableViewCell *)tableView:(UITableView *)tableView1 cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger row = [indexPath row];
NSString *contentForThisRow = nil;
NSString *contentForThisRow2 = nil;
if (mySearchBar.text > 0)
{
contentForThisRow = [self.filteredListContent objectAtIndex:row];
NSInteger noWordIndex = [self.noWords indexOfObject:contentForThisRow];
contentForThisRow2 = [self.enWords objectAtIndex:noWordIndex];
NSLog (#"if success?");
}
else
{
contentForThisRow = [self.noWords objectAtIndex:row] ;
contentForThisRow2 = [self.enWords objectAtIndex:row];
NSLog (#"else success?");
}
static NSString *kCellID = #"cellID";
//standard code here etc for this method..
}
The codes above work perfectly except whenever I have used searchBar to filter and then click on Cancel button in the searchBar or Search button in the keyboard and then when I click on my custom "change" button in the navigationbar, the app crashes.
Before I use searchBar, there show up 4 NSLog after each change like:
2011-08-15 17:21:24.481 Enne1[4750:207] else success?
2011-08-15 17:21:24.483 Enne1[4750:207] else success?
2011-08-15 17:21:24.484 Enne1[4750:207] else success?
2011-08-15 17:21:24.485 Enne1[4750:207] else success?
And when I use searchBar to filter words, there show up also 4 NSLog like this:
2011-08-15 17:19:33.713 E1[4744:207] if success?
2011-08-15 17:19:33.714 E1[4744:207] if success?
2011-08-15 17:19:33.714 E1[4744:207] if success?
2011-08-15 17:19:33.715 E1[4744:207] if success?
But when after I have used searchBar and then cleared the searchText either with Cancel or Search and then click on "change button", there show up only 1 NSLog like this:
2
011-08-15 17:21:49.806 E1[4750:207] if success?
It should be
else success
in order to show the full lists, not
if success
.
Am I missing something?
EDIT 15 august:
I have tried
if(mySearchBar.text.length > 0)
as well, but the tableview shows nothing when I clear my search string and there came up only 2 nslogs, that is:
2011-08-15 23:49:06.624 E1[5064:207] if success?
2011-08-15 23:49:06.626 E1[5064:207] if success?
By the way, why does it show up 4 nslogs each time I enter one alphabet in the search bar? Shouldnt it show only one nslog each time?
And my codes for textDidChange is:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchString
{
NSLog (#" ss: %#", searchString);
if ([searchString length] == 0) {
[self performSelector:#selector(hideKeyboardWithSearchBar:) withObject:searchBar afterDelay:0];
NSLog (#" searchstring: %#", searchString);
}
[self filterContentForSearchText:searchString];
[tableView reloadData];
NSLog (#"has reloaded!");
return;
}
Edit 15 august; This is wrong: I suspect the code above is causing the app crashing? not reloading tableview properly?
Am I right? NSLog for searchString showed nothing...
2nd edit 15 august: I added NSLog (#" ss: %#", searchString); and of course it shows alphabet(s) each time I enter one alphabet. So it must be something wrong with mySearchBar.text > 0, how should I write this properly?
By the way, I added tableview and searchbar programmatically, tableviews delegate and datasource is linked to self and searchbars delegate is linked to self as well. There is nothing in InterfaceBuilder, only UIView.
Not really sure what you're attempting with
if(mySearch.text > 0) {
//stuff
}
Looks, like you're trying to compare the length to see if the string is empty. Try using this instead:
if([mySearchBar text] == nil || ![[mySearchBar text] isEqualToString:#""]) {
//stuff
}
Getting into this code block is probably what the problem is. Not sure how your objects are implemented, but if the filtered list is nil, then you would crash trying to get objects from it and what not.
You should definitely use if(mySearchBar.text.length > 0), not if(mySearchBar.text > 0).
It probably crashes here:
contentForThisRow2 = [self.enWords objectAtIndex:noWordIndex];
because noWordIndex was -1 (i.e. 2147483647) in the previous line. It'll crash this way even if you just type in a word that doesn't exist in the noWords array, so you need to check if noWordIndex is >= 0 before using it to access enWords. This will probably fix the problem with cleaning the search text, too.
By the way, a much faster way to look up words would be using an NSDictionary instead of two arrays.
Ah, I solved it by adding length to mySearchBar.text; mySearchBar.text.length > 0 works. I forgot to rewrite in another method, I changed mySearchBar.text to mySearchBar.text.length, that is:
- (NSInteger)tableView:(UITableView *)tableView1 numberOfRowsInSection:(NSInteger)section
{
tableView1.rowHeight = 100 ;
tableView1.separatorColor = [UIColor colorWithRed:0.40 green:0.70 blue:0.45 alpha:1.0];
tableView1.opaque = NO;
if (mySearchBar.text.length > 0)
{
return [self.filteredListContent count];
NSLog (#"if return");
}
else
{
return [self.noWords count];
NSLog (#"else return");
}
}
#Daniel R Hicks
and
#ColdLogic: So both you are right that it is wrong to use only mySearchBar.text. Thank you very much for pointing me in the right direction.
But I still wonder why there come up 4 nslogs each time...
EDIT 16 august:
4 nslogs show up every time I launch the app, because there are 4 visible cells. My tableview.height is 100, so when I changed it to 50, 8 nslogs show up and as well as 8 visible cells.
In my viewDidLoad, I have self.todaySession = (id)[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; which works fine when it finds this object. But when it doesn't, it crashes the app. Is there a logic statemnt I can do to fix this?
There are a few ways of doing this depending on the context.
You can use the count of fetchedObjects or sections from the NSFetchedResultsController. Make sure you call performFetch: beforehand.
[fetchedResultsController performFetch:self];
// Option 1
BOOL someResultsReturned = ([[fetchedResultsController fetchedObjects] count] > 0);
// Option 2
BOOL someResultsReturned = ([[fetchedResultsController sections] count] > 0);
if (someResultsReturned) {
self.todaySession = (id)[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
} else {
// Handle no results here
}
More Information
Read more about NSFetchedResultsController here.
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'm new to iPhone dev and programming in general, having recently completed Stephen Kochan's Programming in Objective-C 2.0, and parts of Erica Sadun's iPhone Cookbook. Just reading answered questions on this site has helped me a lot, so I owe a big thanks to all its users. Now I'm working on my first iPhone app, and am making good progress, save for one thing that's got me stumped.
I'm having some trouble with a UITableView. Basically, I have a table that needs to have a maximum of 6 rows per section, with no minimum (except if the row of a 1-row section is deleted, in which case the section goes along with it). The table can also be reordered by the user. When the user drags a row into a section that already has the maximum 6 allotted rows, I want the bottom row of that section to sort-of 'budge' down to become the top row of the next section.
As for implementing this, my first thought was to call a method in tableView:moveRowAtIndexPath:toIndexPath: that would go through the sections in a loop, making sure that none of them had 7+ rows, adjusting the array as needed, and then using a [myTable beginUpdates] block to delete and insert the required rows. This is what all that looks like (note: my array structure is: Array (Table) > Arrays (Sections) > Dictionaries (Items)):
-(void) tableView: (UITableView *) tableView moveRowAtIndexPath: (NSIndexPath *) from toIndexPath: (NSIndexPath *) to
{
// Get the dictionary object for the icon.
NSMutableDictionary *theIcon = [[[TABLE_ARRAY_MACRO objectAtIndex: from.section] objectAtIndex: from.row] mutableCopy];
// Now remove it from the old position, and insert it in the new one.
[[TABLE_ARRAY_MACRO objectAtIndex: from.section] removeObjectAtIndex: from.row];
[[TABLE_ARRAY_MACRO objectAtIndex: to.section] insertObject: theIcon atIndex: to.row];
if ( [[TABLE_ARRAY_MACRO objectAtIndex: to.section] count] > 6 )
[self budgeRowsAtSection: to.section];
// Now we're done with the dictionary.
[theIcon release];
if ( PM_DEBUG_MODE )
NSLog(#"Pet moved!");
}
-(void) budgeRowsAtSection: (NSUInteger) section
{
if ( PM_DEBUG_MODE )
NSLog(#"Budging rows...");
// Set up an array to hold the index paths of the cells to move.
NSMutableArray *budgeFrom = [[NSMutableArray alloc] init];
NSMutableArray *budgeTo = [[NSMutableArray alloc] init];
// Start at the current section, and enumerate down to the nearest page with < 6 items.
int i = section;
while ( i < [TABLE_ARRAY_MACRO count] ) {
if ( PM_DEBUG_MODE )
NSLog(#"Attempting to budge rows in section %i!", i);
if ( [[TABLE_ARRAY_MACRO objectAtIndex: i] count] > 6 ) {
// Grab the last object, and move it to the beginning of the next array.
NSMutableDictionary *lastIcon = [[[PET_ICON_DATA objectAtIndex: i] lastObject] mutableCopy];
[[TABLE_ARRAY_MACRO objectAtIndex: i] removeLastObject];
[[TABLE_ARRAY_MACRO objectAtIndex: (i + 1)] insertObject: lastIcon atIndex: 0];
// Create an index path, and reflect the changes in the table.
[budgeFrom addObject: [NSIndexPath indexPathForRow: 6 inSection: i]];
[budgeTo addObject: [NSIndexPath indexPathForRow: 0 inSection: (i + 1)]];
// Now we're done with the icon.
[lastIcon release];
}
if ( PM_DEBUG_MODE )
NSLog(#"Rows budged for section %i!", i);
++i;
}
if ( PM_DEBUG_MODE )
NSLog(#"From cells: %#\nTo cells: %#", budgeFrom, budgeTo);
if ( PM_DEBUG_MODE )
NSLog(#"Data budged! Updating table...");
[editTable beginUpdates];
[editTable deleteRowsAtIndexPaths: budgeFrom withRowAnimation: UITableViewRowAnimationBottom];
[editTable insertRowsAtIndexPaths: budgeTo withRowAnimation: UITableViewRowAnimationTop];
[editTable endUpdates];
[budgeFrom release];
[budgeTo release];
if ( PM_DEBUG_MODE )
NSLog(#"Row budge done!");
}
The problem is when I run this, it invariably throws an exception; it gives me either Invalid update: number of rows in section after update must be equal to number of rows before update, + or - the number inserted or deleted (etc.), or Attempted to create two animations for cell, depending upon which tweak I'm trying. As for what those tweaks are, I've also tried using reloadSections:withRowAnimation:, and of course a simple [editTable reloadData]. I've tried placing the call to this method in the relevant TableView delegate/data source methods, including targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:, and the somewhat mysterious willMoveToRowAtIndexPath:fromIndexPath: method that is mentioned in the Apple guide on TableViews, but is non-existent elsewhere.
I've searched all over the Google, this site, and Apple's docs for any inkling of a solution, but to no avail.
So, to put my question simply, what is the best way to go about this? Is there some obvious thing that I'm overlooking? Or is my implementation concept just fundamentally flawed from the get-go?
Thanks in advance for the help! Any insight would be much appreciated.
-- Drew R. Hood
UPDATE: Following Jordan's advice from his comment on his answer, I've verified that the actual edits to the data source are going through correctly. I now consistently get the error Attempt to create two animations for cell when the table edit block executes (that timing verified by common sense and breakpoints). So I'm doing something wrong in my edit block. The current code for that is exactly as shown above. Ideas?
[editTable beginUpdates]; [editTable
deleteRowsAtIndexPaths: budgeFrom
withRowAnimation:
UITableViewRowAnimationBottom];
[editTable insertRowsAtIndexPaths:
budgeTo withRowAnimation:
UITableViewRowAnimationTop];
[editTable endUpdates];
I believe you need to update the DataSource before you actually delete the Row in whatever section you're deleting it from. So in this code, before you do the delete, delete the row from Table_Array_Macro (which I think is where your data is).
So, I've finally solved the problem and thought I'd post what I did here for the sake of posterity. It's quite simple, really: I just moved the actual insertion and deletion of rows in the table off to a separate method, which I then invoked after a delay. It had been clear from the start, and Jordan helped me to confirm, that the problem was occurring when performing the updates on the table itself, rather than in the process of actually changing data. So here's the code that did the trick:
-(void) performBudges
{
if ( PM_DEBUG_MODE )
NSLog(#"Performing budge animations...");
[editTable beginUpdates];
[editTable deleteRowsAtIndexPaths: budgeFrom withRowAnimation: UITableViewRowAnimationRight];
[editTable insertRowsAtIndexPaths: budgeTo withRowAnimation: UITableViewRowAnimationRight];
[editTable endUpdates];
[editTable performSelector: #selector(reloadData) withObject: nil afterDelay: 0.5];
[reloadedSections release];
[budgeFrom release];
[budgeTo release];
if ( PM_DEBUG_MODE )
NSLog(#"Budges completed!");
}
And all I have to do to perform this is add this snippet into my moveRowAtIndexPath: method:
if ( [[PET_ICON_DATA objectAtIndex: to.section] count] > 6 ) {
[self budgeRowsAtSection: to.section];
[self performSelector: #selector(performBudges) withObject: nil afterDelay: 1.0];
}
So I hope this helps anyone who may have this problem in the future. Thanks to Jordan and all who viewed this question. :)