EXC_BAD_ACCESS on ReloadData - iphone

I'm loading data dynamically from an API using a UISearchBar and trying to display it, using something like this:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
NSLog(#"Search Text: %#",[searchBar text]);
[self startSearchingWithText:[searchBar text]];
}
- (void) startSearchingWithText:(NSString *)text {
...
// Go through the list of services and set up a query for the search term
QueryServiceManager *manager = [[QueryServiceManager alloc] initWithServices: services];
// Set up the query
if (![manager addKeyword: text])
NSLog(#"PROBLEM!");
// Execute the query and store the titles in an array
self.data = [[manager makeQuery] copy]; // Returns a NSArray
NSLog(#"%#",self.data);
// Add the items to the tableView
[self.tableView reloadData];
My UITableViewController code is set up to read from self.data, which is initially a NSArray of #"" elements, as such:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
[self.data count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
cell.textLabel.text = [self.data objectAtIndex:indexPath.row];
return cell;
}
All I get when I do this is:
(gdb) continue
2010-02-26 18:43:32.515 iSave[11690:207] (
"Toshiba Satellite L505-GS5037 TruBrite 15.6-Inch Laptop (Black)",
"Dell Inspiron 11 11.6-Inch Obsidian Black Laptop (Windows 7 Premium)",
"ASUS Eee PC Seashell 1005PE-MU17-BK 10.1-Inch Black Netbook - Up to 11 Hours of Battery Life",
"SwissGear Computer Backpack (Red)",
"Belkin 36-Piece Demagnatized Computer Tool Kit with Case (Black)",
"Compaq EVO N610C",
"Belkin F8E062 55-Piece Computer Tool Kit with Black Case (Demagnetized Tools)",
"ASUS Eee PC Seashell 1005PE-PU17-BK 10.1-Inch Black Netbook - Up to 14 Hours of Battery Life",
"Harman Kardon SoundSticks II 2.1 Plug and Play Multimedia Speaker System",
"ION Audio VCR 2 PC USB VHS Video to Computer Converter"
)
(gdb) continue
Program received signal: “EXC_BAD_ACCESS”.
(gdb)
Any ideas? It looks like the NSArray is being populated properly, then things are failing on/after the reloadData call (breakpoints confirm this, but can't isolate where things are going wrong)
EDIT: I replaced theNSLog() with enumeration
for ( NSString *elem in self.data )
NSLog(elem);
And it still crashes. I managed to coax a log out of it: http://pastebin.com/NDVKLsJC
When I remove the NSLog() entirely, it doesn't crash, but the UITableViewdoesn't update, either.

Have you identified the object which you're trying to access but which no longer exists, the object which is giving EXC_BAD_ACCESS?
If not then you should enable Zombies in your app, then you can issue a command to gdb which will tell you which object is causing the crash.

Something weird: You're saying that you only have 1 row in each section, yet you're using indexPath.row to index into your array? One of those is wrong, and I'm guessing it's the first. Usually if you're displaying an array (of n elements) in a UITableView, you have one section and n rows in that section, which means you'd use [myArray objectAtIndex:indexPath.row] to retrieve the appropriate object.
If you want a separate section for each item in the array, then you say that you have n sections, and 1 row in each section, and then use [myArray objectAtIndex:indexPath.section] to retrieve the appropriate object.
Oh, and you're leaking memory all over the place.

What about:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.data count];
}
instead of always returning 1.

Hit shift-command-b to analyze the project and solve all problems displayed (potential leaks, etc.)

Related

Why my uitableview getting stretched when displaying hundreds of record in ipad

This is the code that i am using to display my tableview custom cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"CustomCell";
CustomeCellHome *cell = (CustomeCellHome *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomeCellHome" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (CustomeCellHome *) currentObject;
break;
}
}
}
NSString *strImgUrl = [[[[myarray valueForKey:#"mykey1"]objectAtIndex:indexPath.row] valueForKey:#"url"] objectAtIndex:0];
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:cell.imgView];
cell.imgView.imageURL = [NSURL URLWithString:strImgUrl];
cell.lbl1.text = [[myarray valueForKey:#"mykey2"]objectAtIndex:indexPath.row];
cell.lbl2.text = [[myarray valueForKey:#"mykey3"]objectAtIndex:indexPath.row];
cell.lbl3.text = [[myarray valueForKey:#"mykey3"]objectAtIndex:indexPath.row];
cell.lbl4.text = [[myarray valueForKey:#"mykey4"]objectAtIndex:indexPath.row];
cell.lbl5.text = [[myarray valueForKey:#"mykey5"]objectAtIndex:indexPath.row];
cell.lbl6.text = [[myarray valueForKey:#"mykey6"]objectAtIndex:indexPath.row];
}
CustomeCellHome is sub class of UITableViewCell and i am using Nib to display it. CustomeCellHome have all the IBOutlet and connected to Nib. All IBOutlet is property and synthesize and i have released that in dealloc method.
I am displaying 50 records in tableview at a time. If user want to see more record he can see that by clicking on next button and also view the previous record by clicking on previous button.
When user presses next button i am calling web service and getting next 50 record from the server. I am keeping my previous 50 records in the array and adding this new records to that array. But in "myarray" i am keeping only 50 at a time to display in tableview (Means i am displaying 50 records only at a time).
When i reach to view the 850 to 900 or near to that record my table view is getting stretched and it will stopped and displaying after some time.
Also after viewing 1450 to 1500 or near to that record i am receiving memory warning and my app getting crashed. I am not getting this. Why is it so ?
I have checked with memory tool i didn't get any memory leak. Am i using wrong to display the custom cell or my web service call causes more memory ?
Please any one can guide me for this. Thanks in advance.
It sounds like you are consuming too much memory. You don't need to leak memory to have your app shot by the watchdog.
I would profile the memory consumption of your app as you load more and more data. You'll probably want to respond to a memoryWarning message and delete all of your objects except for the current set. You could (de)serialize the objects to disc to preserve user's bandwidth.
You don't show the lifespan of your objects, but assuming they are fetched/stored in your UI(Table)ViewController you'll want to implement code in - (void)didReceiveMemoryWarning or - (void)viewDidUnload (iOS5 vs. iOS6) to clear out the older data.
SideNote: is myarray actually a dictionary? It's good practice to give your variables descriptive names to make your code easier to understand (for others, and yourself in 2 months when you need to fix a bug).

iOS 5: Executing if statements inside of a UITableView delegate method

I'm currently writing an application that's meant to scale with data brought in from the back-end. To test this scalability, I'm currently using plists to bring in some basic data.
The plan is to eventually have male/female icons appear next to the user names for gender/visual purposes (using colors currently), however, after writing a conditional statement in the proper tableView method, the application seems to skip those arguments entirely.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SelectProfileCell *cell = (SelectProfileCell *) [tableView dequeueReusableCellWithIdentifier:#"SelectProfileCell"];
NSArray *array = [[NSArray alloc] initWithArray:[userArray objectAtIndex:indexPath.row]];
cell.nameLabel.text = [array objectAtIndex:0];
gender = [array objectAtIndex:1];
if(gender == #"Male"){
cell.genderLogo.backgroundColor = [UIColor blueColor];
NSLog(#"Male");
}
if(gender == #"Female"){
cell.genderLogo.backgroundColor = [UIColor greenColor];
NSLog(#"Female");
}
return cell;
}
In this instance, both userArray and gender are instance variables that have already been initialized. "userArray" has already been passed the appropriate 2D Array data from the property list (used the console to check this).
Again, the log is not showing the "Male"/"Female" debug messages, so these "if" statements are being skipped for some reason. Any suggestions?
Thank you!
use [gender isEqualToString:#"Male"] instead of ==

UITableView lazy loading optimization

I have UITableView. in tableView:cellForRow:atIndexPath: method (when data populates to cells) I've implemented some kind of lazy loading. If there's no object for key(key==row number) in rowData NSDictionary program launches requestDataForRow: method in background. so the data in cells gets populated a bit after the cell becomes visible.
Here's the code:
static int requestCounter=0;
-(void)requestDataForRow:(NSNumber *)rowIndex
{
requestCounter++;
//NSLog(#"requestDataForRow: %i", [rowIndex intValue]);
PipeListHeavyCellData *cellData=[Database pipeListHeavyCellDataWithJobID:self.jobID andDatabaseIndex:rowIndex];
[rowData setObject:cellData forKey:[NSString stringWithFormat:#"%i", [rowIndex intValue]]];
requestCounter--;
NSLog(#"cellData.number: %#", cellData.number);
if (requestCounter==0)
{
//NSLog(#"reloading pipe table view...");
[self.pipeTableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
};
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"pipeCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
[[NSBundle mainBundle] loadNibNamed:#"PipesForJobCell" owner:self options:nil];
cell = pipeCell;
self.pipeCell = nil;
PipeListHeavyCellData *cellData=[[PipeListHeavyCellData alloc] init];
if ([rowData objectForKey:[NSString stringWithFormat:#"%i", indexPath.row]]==nil)
{
//NSLog(#" nil data for row: %i", indexPath.row);
[self performSelectorInBackground:#selector(requestDataForRow:) withObject:[NSNumber numberWithInt:indexPath.row]];
}
else
{
//NSLog(#" has data for row: %i", indexPath.row);
PipeListHeavyCellData *heavyData=[[PipeListHeavyCellData alloc] init];
heavyData=(PipeListHeavyCellData *)[rowData objectForKey:[NSString stringWithFormat:#"%i", indexPath.row]];
cellData._id=[heavyData._id copy];
cellData.number=[heavyData.number copy];
cellData.status=[heavyData.status copy];
};
This code works, everything is OK, BUT my table has 2000 rows and If users scrolls from cell with index 10 to cell with index 2000 very quickly. He must wait for a long time until all pulling data requests will complete (for rows 11, 12, 13, ..., 2000) cause that rows became visible while user was scrolling table view so the method requestDataForRow was called for them.
How can I optimize those things?
I had to do something similar. You'll need to create a a queue that processes the most recently added items first.
For example, the user opens the table and 10 requests are queued up. You dequeue the first object and start fetching the data for the first row. However, the user then scrolls down to rows 31-40. You'll then have to insert those rows before the first 10 in your queue because they are now higher priority. The key is that you don't immediately start 10 requests at once, but process them in order. That way, when the user scrolls, you only "waste" one request - the last request that was made.
An easy way to actually implement this is to use [tableView indexPathsForVisibleRows].

UITextField artifacts

I have an iPhone app which has a Table View-based data input screen with a toggle, which when on shows all rows in another section of the table.
Sometimes, when the app is first loaded, and usually when it has been fully deleted from the phone, the UITextFields from the original section of the table are displayed in addition to the new rows, as below (main table section is above)
:
THe strangest thing about this is that this behaviour only occurs the first time this screen is displayed - it seems fine after that. Oh, and it only seems to occur on the phone, not the simulator.
Given the random nature of this, could it have something to do with other apps? I did have apps with similar namespaces running at the same time. The problem seemed to go away after I closed the apps down / deleted from phone.
I have included the code block that is run when the switch is changed below:
- (void)accountSwitchChanged {
[customerDetails saveField:#"addToAccount" WithBool:addToAccountSwitch.on];
NSArray *indexes = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:1 inSection:1], [NSIndexPath indexPathForRow:2 inSection:1], [NSIndexPath indexPathForRow:3 inSection:1],nil];
if (addToAccountSwitch.on)
[detailsTable insertRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationFade];
else
[detailsTable deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationFade];
}
Any ideas?
--EDIT
cellForRowAtIndexPath code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// get rid of grey background for iPad app
[tableView setBackgroundView:nil];
UITableViewCell *cell = nil;
NSDictionary *cellData = [[dataSourceArray objectAtIndex: indexPath.section] objectAtIndex:indexPath.row];
id cellControl = [cellData objectForKey:kCellControlKey];
static NSString *kCellControl = #"CellControl";
cell = [tableView dequeueReusableCellWithIdentifier:kCellControl];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellControl] autorelease];
cell.textLabel.textColor = [[[ConfigManager sharedInstance].skin valueForKey:#"tableCellHeadingTextColour"] toUIColor];
cell.backgroundColor = [[[ConfigManager sharedInstance].skin valueForKey:#"tableCellBgColour"] toUIColor];
cell.textLabel.font = [UIFont boldSystemFontOfSize:17];
} else {
// a cell is being recycled, remove the old control (if it contains one of our tagged edit fields)
UIView *viewToCheck = nil;
viewToCheck = [cell.contentView viewWithTag:kDetailsViewTag];
if (!viewToCheck)
[viewToCheck removeFromSuperview];
}
// if control is not a text field, make it a button
if (![cellControl isKindOfClass:[UITextField class]])
cell.selectionStyle = UITableViewCellSelectionStyleGray;
else
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = [cellData objectForKey:kCellTitleKey];
[cell.contentView addSubview:cellControl];
return cell;
}
Something to check and eliminate If it is only occurring the first time view is displayed, it's always worth checking whether you are assuming the view is loaded before you are doing work on it. This doesn't come up very often, but when it does it can be very hard to track down. To check whether this might be your issue, add a [viewController view] call just after you create the view for the first time. This forces the view to be loaded. If the issue goes away when you add this, you've tracked the source of the issue. And you can even leave the [viewController view] call in as a fix, or work through your code to allow for lazy instantiation.
All this said, far more likely to be something funny in your tableView:cellForRowAtIndexPath: code. If you want good stack overflow, post all or relevant fragment of the that code (or the relevant code it calls).
Follow up on posted code:
(1) if (!viewToCheck) [viewToCheck removeFromSuperview] won't do anything. It'll only send a removeFromSuperview message when viewToCheck is nil and that won't do anything. Not sure if this will be key issue, but it's something to put right.
(2) [cell.contentView addSubview:cellControl]; - this looks problematic - it's usually best to only add subviews when you create the cell. You can hide or show in the main body of tableView:cellForRowAtIndexPath: - that would, I suspect, work for you here. With this code there's a risk here that you'll either add multiple subviews when you only want one or (which may be what's going on here) that you end up not removing a subview when a cell is recycled. If the viewToCheck code is supposed to be removing the added cellControl then, as (1), it won't do that just now because your if condition is wrong.

How to reload a UITableView for more data?

I have UITableView, which I fill from a JSON Query.
I fill it with Private Messages and it works.
The JSON Query has the properties of "site" in which I get for every site 20 PMs.
So at the beginning I got Site=0 and got 20PMS which are loaded.
Now I wanna have the features like in the Email-app (i believe I saw it there): When you scroll down and reach the end (in my app reached the 20. PM), the application should load the next 20 and so on and so on.
Any ideas how to realize?
Try
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return ([array count] + 1);
}
In
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Do
if([indexpath row] == [array count]) {
[self loadPMForSite:([array count]/20-1)];
}
In
- (void)loadPMForSite:(int)siteNumber
load the new pms and then reload the tableview data.
Not tested but supposed to work. May include typos.
Sideswipe's answer is a good start, but you don't want to freeze the UI while you get the next twenty items. In the last-row case, you should return a cell with "Loading..." and a UIActivityIndicator while launching a thread to actually get the data. When that thread completes, then it signals the main thread, which can then reload the data. As an aside, be careful not to change the size of array (in background thread) until you actually putting in the data, as you may run into a problem with the user scrolling beyond the last line if [array count] is too large.
See apple's sample: LazyTableImages
For the UIActivityIndicator:
UIActivityIndicatorView * loading = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(200, 20, 30, 30)];
[date setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
[self.view addsubview:loading];
[loading startAnimating];