I have an App that compiles a list of customer specific reports for a customer after login. These reports are showing up and I have put a "view" button which is supposed to download the PDF file, and bring it up to view within the app.
At this stage, it looks as though I have a memory issue when the view button is pressed, and I am not sure how to find where the issue is. HEre is my code:
#import "reportsTestViewController.h"
#import "ReportsDataObject.h"
#import "Session.h"
#import "AFNetworking.h"
#interface reportsTestViewController ()
#end
#implementation reportsTestViewController
#synthesize response;
#synthesize myDataIvar;
#synthesize viewReportPressed;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
reportsTable.delegate = self;
reportsTable.dataSource = self;
self.sections = [[NSMutableDictionary alloc] init];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
#pragma mark NSURLConnection Delegate Methods
//
//Create your request pointing to the test page
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.tesg.com.au/allCustBuild.php"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//initialize it when you create your connection
if (connection){
self.myDataIvar = [[NSMutableData alloc] init];
}
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[self.myDataIvar setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.myDataIvar appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Connection Failed: %#", error.userInfo);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
//this is where you would parse the data received back from the server
NSString *responseString = [[NSString alloc] initWithData:self.myDataIvar encoding:NSUTF8StringEncoding];
NSLog(#"Received Data: %#",responseString);
[self setupReportsFromJSONArray:self.myDataIvar];
}
-(void)connectionWasASuccess:(NSData *)data{
[self setupReportsFromJSONArray:data];
}
-(void)setupReportsFromJSONArray:(NSData*)dataFromReportsArray{
BOOL found;
NSError *error;
// NSMutableArray *reportsArray = [[NSMutableArray alloc] init];
NSArray *arrayFromServer = [NSJSONSerialization JSONObjectWithData:dataFromReportsArray options:0 error:&error];
if(error){
NSLog(#"error parsing the json data from server with error description - %#", [error localizedDescription]);
}
else {
reportsArray = [[NSMutableArray alloc] init];
for(NSDictionary *eachReport in arrayFromServer)
{
ReportsDataObject *report = [[ReportsDataObject alloc] initWithJSONData:eachReport];
[reportsArray addObject:report];
NSString *c = [[eachReport objectForKey:#"title"] substringToIndex:3];
found = NO;
for (NSString *str in [self.sections allKeys])
{
if ([str isEqualToString:c])
{
found = YES;
}
}
if (!found)
{
[self.sections setValue:[[NSMutableArray alloc] init] forKey:c];
}
}
}
NSLog(#"Array Populated");
NSLog(#"%u reports found",reportsArray.count);
//Now you have your reportsArray filled up with all your data objects
for (NSDictionary *eachReport in arrayFromServer)
{
[[self.sections objectForKey:[[eachReport objectForKey:#"title"] substringToIndex:3]] addObject:eachReport];
}
// Sort each section array
for (NSString *key in [self.sections allKeys])
{
[[self.sections objectForKey:key] sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES]]];
}
[reportsTable reloadData];
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)viewReportPressed:(UIButton*)button {
NSLog(#"Button successfully Pressed");
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://tesg.com.au/portal/media/reports/1367365180_367 Collins Passive April 2013.pdf"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *theResponse) {
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[theResponse suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSUInteger count = [[self.sections allKeys] count];
NSLog(#"Number of sections: %d", count);
return count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
//We check against table to make sure we are displaying the right number of cells
// for the appropriate table. This is so that things will work even if one day you
//decide that you want to have two tables instead of one.
{
NSUInteger count = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section]] count];
NSLog(#"Number of rows in section: %d", count);
return count;
}
}
//- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
//return [[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
// }
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
//The beauty of this is that you have all your data in one object and grab WHATEVER you like
//This way in the future you can add another field without doing much.
NSUInteger count = [[self.sections allKeys] count];
if(count == 0){
cell.textLabel.text = #"no reports to show";
}
else{
NSDictionary *Reports = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];
cell.textLabel.text = [Reports objectForKey:#"title"];
cell.detailTextLabel.text = [Reports objectForKey:#"building"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(viewReportPressed:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"View" forState:UIControlStateNormal];
button.frame = CGRectMake(180.0f, 5.0f, 150.0f, 30.0f);
[cell addSubview:button];
// in the future you can grab whatever data you need like this
//[currentReport buildingName], or [currentReport reportName];
}
return(cell);
}
#end
When the view button is pressed in any of the cells, I get the NSLog saying 'button successfully pressed' but it crashes immediately after with error
[downloadTask resume]; Thread 1:EXC_BAD_ACCESS (code=2, address=0x0)
Any ideas how to fix this?
If you get a EXC_BAD_ACCESS with an address of 0x0, that generally means that a nil value was used in a context where nil is not permitted.
In this case, it is a result of the fact that your URL string is not valid. Spaces are not allowed. Thus your resulting URL variable is nil, which will result in the behavior you describe. You probably want to percent escape the URL string, e.g.:
NSString *URLString = #"http://tesg.com.au/portal/media/reports/1367365180_367 Collins Passive April 2013.pdf";
NSURL *URL = [NSURL URLWithString:[URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
To identify this sort of exception in the future, you can use exception breakpoints, which will often help identify the offending line of code. Or just carefully review your code code for situations where you may have unintentionally passed a nil value to a method that does not accept it.
If, however, you ever get a EXC_BAD_ACCESS with a value other than 0x0, that is often a result of an attempt to use a previously deallocated object. (Not always, but frequently. Fortunately, ARC has made this sort of problem less common.) Clearly, this is not the case here, but you asked how one would identify a situation where you attempt to use a previously deallocated object.
To diagnose that situation, you would generally turn on "zombies" (see Running Your Application with Diagnostics). By the way, once you finish your diagnostics with zombies, make sure you turn them back off, as you don't want to keep zombies turned on (especially in a production app).
Anyway, in this case, because the address was 0x0, it wasn't a result of a deallocated object, but rather the problem was the incorrect usage of a nil value, caused by the invalid URL.
Related
I understand that I need to change the data in the data source before calling reloadData. My problem is that I'm not sure how this is done and why my getData method doesn't overwrite the current cells. Is it necessary to use subviews for this? Or is there a way to reset the cells when refresh is called to just create a new set of data?
#property (nonatomic,strong) NSMutableArray *objectHolderArray;
#end
#implementation MartaViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self getData];
//to add the UIRefreshControl to UIView
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Please Wait..."];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
}
- (void)getData
{
NSURL *blogURL = [NSURL URLWithString:JSON_URL];
NSData *jsonData = [NSData dataWithContentsOfURL:blogURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization
JSONObjectWithData:jsonData options:0 error:&error];
for (NSDictionary *bpDictionary in dataDictionary) {
Object *currenHotel = [[Object alloc]Station:[bpDictionary objectForKey:#"station"] Status:[bpDictionary objectForKey:#"status"]];
[self.objectHolderArray addObject:currenHotel];
}
}
- (IBAction)refresh:(UIRefreshControl *)sender {
[self getData];
[self.tableView reloadData];
[sender endRefreshing];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:
(NSInteger)section
{
return [self.objectHolderArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MartaViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
Object *currentHotel = [self.objectHolderArray
objectAtIndex:indexPath.row];
cell.lblStation.text = currentHotel.station;
cell.lblStatus.text = currentHotel.status;
return cell;
}
-(NSMutableArray *)objectHolderArray{
if(!_objectHolderArray) _objectHolderArray = [[NSMutableArray alloc]init];
return _objectHolderArray;
}
#end
Because you are adding objects to self.objectHolderArray instead of overwriting in getData method. Try this
- (void)getData
{
NSURL *blogURL = [NSURL URLWithString:JSON_URL];
NSData *jsonData = [NSData dataWithContentsOfURL:blogURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization
JSONObjectWithData:jsonData options:0 error:&error];
[self.objectHolderArray removeAllObjects];
for (NSDictionary *bpDictionary in dataDictionary) {
Object *currenHotel = [[Object alloc]Station:[bpDictionary objectForKey:#"station"] Status:[bpDictionary objectForKey:#"status"]];
[self.objectHolderArray addObject:currenHotel];
}
}
First initialize the array in viewDidLoad self.objectArray = [NSMutlabelArray alloc] init] and when you are refreshing the table view remove all objects from object array using [self.orderArray removeAllObject] the copy new content in new array.
I have created a app that fetches information from a blog and shows it in the app. After i fetch the data it is supposed to be shown on the table view. It shows the things that i have posted but the things are in alphabetical order rather than the time i posted the thing.
Here is the code
#implementation EventsTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.firstLettersArray = [NSMutableArray array];
self.eventsDictionary = [NSMutableDictionary dictionary];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self searchForEvents];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[self searchForEvents];
}
- (void)searchForEvents
{
[self.searchBar resignFirstResponder];
NSString *eventsSearchUrlString = [NSString stringWithFormat:#"https://www.googleapis.com/blogger/v3/blogs/1562818803553764290/posts?key=AIzaSyBTOxz-vPHgzIkw9k88hDKd99ILTaXTt0Y"];
NSURL *eventsSearchUrl = [NSURL URLWithString:eventsSearchUrlString];
NSURLRequest *eventsSearchUrlRequest = [NSURLRequest requestWithURL:eventsSearchUrl];
NSURLSession *sharedUrlSession = [NSURLSession sharedSession];
NSURLSessionDataTask *searchEventsTask =
[sharedUrlSession dataTaskWithRequest:eventsSearchUrlRequest completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),
^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if(error)
{
UIAlertView *searchAlertView = [[UIAlertView alloc] initWithTitle:#"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[searchAlertView show];
}
else
{
NSString *resultString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Search results: %#", resultString);
NSError *jsonParseError = nil;
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];
if(jsonParseError)
{
UIAlertView *jsonParseErrorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:jsonParseError.localizedDescription delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[jsonParseErrorAlert show];
}
else
{
for(NSString *key in jsonDictionary.keyEnumerator)
{
NSLog(#"First level key: %#", key);
}
[self.firstLettersArray removeAllObjects];
[self.eventsDictionary removeAllObjects];
NSArray *searchResultsArray = [jsonDictionary objectForKey:#"items"];
//NSLog(#"test%#",searchResultsArray);
for(NSDictionary *eventsInfoDictionary in searchResultsArray)
{
Events *event = [[Events alloc] init];
event.eventName = [eventsInfoDictionary objectForKey:#"title"];
event.eventDescription =[eventsInfoDictionary objectForKey:#"content"];
NSLog(#"Event Name : %#",event.eventName);
NSLog(#"Event Description : %#",event.eventDescription);
NSString *eventsFirstLetter = [event.eventName substringToIndex:1];
NSMutableArray *eventsWithFirstLetter = [self.eventsDictionary objectForKey:eventsFirstLetter];
if(!eventsWithFirstLetter)
{
eventsWithFirstLetter = [NSMutableArray array];
[self.firstLettersArray addObject:eventsFirstLetter];
}
[eventsWithFirstLetter addObject:event];
[self.eventsDictionary setObject:eventsWithFirstLetter forKey:eventsFirstLetter];
if ([event.eventDescription containsString:#"<br />"]) {
NSString* eventDescrip = event.eventDescription;
NSString* stringWithoutHTMLtags = [eventDescrip stringByReplacingOccurrencesOfString:#"<br />" withString:#""];
event.eventDescription = stringWithoutHTMLtags;
}
NSLog(#"Event Name : %#",event.eventName);
NSLog(#"Event Description : %#",event.eventDescription);
}
[self.firstLettersArray sortUsingSelector:#selector(compare:)];
[self.tableView reloadData];
}
}
});
}];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[searchEventsTask resume];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.firstLettersArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *firstLetter = [self.firstLettersArray objectAtIndex:section];
NSArray *eventsWithFirstLetter = [self.eventsDictionary objectForKey:firstLetter];
return eventsWithFirstLetter.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"eventTitleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *firstLetter = [self.firstLettersArray objectAtIndex:indexPath.section];
NSArray *eventsWithFirstLetter = [self.eventsDictionary objectForKey:firstLetter];
Events *event = [eventsWithFirstLetter objectAtIndex:indexPath.row];
cell.textLabel.text = event.eventName;
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellText = cell.textLabel.text;
NSLog(#"Row selected %#",cellText);*/
NSString *firstLetter = [self.firstLettersArray objectAtIndex:indexPath.section];
NSArray *eventsWithFirstLetter = [self.eventsDictionary objectForKey:firstLetter];
Events *event = [eventsWithFirstLetter objectAtIndex:indexPath.row];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
DescriptionViewController *descriptionViewController = (DescriptionViewController*)[mainStoryboard instantiateViewControllerWithIdentifier: #"descriptionController"];
descriptionViewController.eventNameDesc = event.eventDescription;
descriptionViewController.navigationItem.title = event.eventName;
[self.navigationController pushViewController:descriptionViewController animated:YES];
}
#end
You are trying to sort firstLettersArray which contain Events objects by using standard sort: function which doesn't know how to work with your custom objects.
You can use sortedArrayUsingComparator: function like this:
[firstLettersArray sortedArrayUsingComparator:^NSComparisonResult(Events *obj1, Events *obj2) {
// return object comparison result here
}];
Edit: Also you need to have NSDate property in Events class and feel it with event created time. I believe event created time should be contained in eventsInfoDictionary. Eventually you will be able to compare obj1 and obj2 using NSDate property.
i think this line not working
[self.firstLettersArray sortUsingSelector:#selector(compare:)];
use this line of code may help you....
sortedArray = [anArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
I am working on an app for a game server company and part of the app requires the user to see a list of his or her game servers and whether or not they are online, offline, how many players on them, the server name, etc. This data is all found in a PHP file hosted on the web updated from a MySQL database which when viewed, outputs JSON.
Using the code below, this doesn't seem to work. I load the view and right away get a "Thread 1: signal SIGABRT" error on the line with NSDictionary *myServer = [servers objectAtIndex:indexPath.row];. When removing indexPath.row and replacing it with either a 0 or 1, the data is displayed on the UITableView in my Storyboard, except it is displayed 4 times in a row and only for that entry in the JSON file (either 0 or 1). I can't keep it at a fixed number as the client might have 100 servers, or just 5 servers which is why I need something like indexPath.row. Below, I also attached exactly what the JSON looks like when given from the server and accessed directly from the app's code
I'd really appreciate it if someone could please let me know what the problem is and propose a solution unique to my situation to get rid of this SIGABRT error and once we do, make sure it doesn't show 4 times in the TableView like it is now.
My header file:
#import <UIKit/UIKit.h>
#import "ServerDetailViewController.h"
#interface SecondViewController : UITableViewController {
IBOutlet UITableView *mainTableView;
NSDictionary *news;
NSMutableData *data;
}
#property (weak, nonatomic) IBOutlet UIBarButtonItem *refreshServersButton;
- (IBAction)refreshServers:(id)sender;
#end
My main file:
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:#"REDACTED"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
news = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
[mainTableView reloadData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Unable to load server list. Make sure you are connect to either 3G or Wi-Fi or try again later." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil, nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [news count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIColor *colorGreen = [UIColor colorWithRed:91.0f/255.0f green:170.0f/255.0f blue:101.0f/255.0f alpha:1.0f];
UIColor *colorRed = [UIColor redColor];
static NSString *CellIdentifier = #"MainCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UILabel *serverName = (UILabel *)[cell viewWithTag:100];
UILabel *serverPlayers = (UILabel *)[cell viewWithTag:101];
UILabel *serverStatus = (UILabel *)[cell viewWithTag:102];
UILabel *serverOfflineName = (UILabel *)[cell viewWithTag:103];
serverPlayers.textColor = [UIColor grayColor];
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
NSDictionary *myServer = [servers objectAtIndex:indexPath.row];
NSString *titleOfServer = [myServer objectForKey:#"title"];
NSNumber *statusOfServer = [NSNumber numberWithInt:[[myServer objectForKey:#"status"] intValue]];
NSNumber *playersOnServer = [NSNumber numberWithInt:[[myServer objectForKey:#"players"] intValue]];
if ([[statusOfServer stringValue] isEqualToString:#"0"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = colorRed;
serverStatus.text = #"OFFLINE";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"1"]) {
serverName.text = titleOfServer;
serverOfflineName.text = #"";
serverStatus.textColor = colorGreen;
serverStatus.text = #"ONLINE";
serverPlayers.text = [playersOnServer stringValue];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"2"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor blueColor];
serverStatus.text = #"BUSY";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"3"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor grayColor];
serverStatus.text = #"SUSPENDED";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"-1"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor orangeColor];
serverStatus.text = #"CRITICAL ERROR";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ServerDetailViewController *detail = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
[self.navigationController pushViewController:detail animated:YES];
}
- (IBAction)refreshServers:(id)sender {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:#"REDACTED"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
#end
JSON code from server: {"status":"OK","error":"","debug":"2 server(s)","result":{"servers":[{"id":1,"title":"Test","players":0,"slots":10,"status":3},{"id":2,"title":"Creative Spawn","players":0,"slots":5,"status":-1}]}}
From your code, this looks like the source of error.(However, I didn't read the whole thing.)
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [news count]; //counted number of items in your whole json object.
}
and in your cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
// you used a different array(an item of the whole json array).
// Since news object has more items than servers, it caused an out of bound here.
NSDictionary *myServer = [servers objectAtIndex:indexPath.row];
Try doing following to your code
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
return [servers count]; //counted number of items in your whole json object.
}
TL;DR
The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).
There's a few things here:
Firstly, this is not a very proficient way of networking, since you support iOS5, I would suggest using the following method (or something similar) :
[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *dataString = [[NSString alloc] initWithBytes:[data bytes] length:[[data bytes] length] encoding:NSUTF8StringEncoding];
}];
Secondly, I would strongly recommend the MVC model, managing data is not something for a controller - as someone has mentioned, the code at the moment isn't as easy reading as it could be (as well as maintaining it!).
Thirdly, I would recommend you employ more defensive coding, below are the points I've spotted while reading through:
news, well rather [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];,, isn't guaranteed to return a dictionary at all; although it is treated as such
The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).
If I were you, I'd probably start with simplifying the networking, followed by created a simple model (for example Server), have the model parse the JSON that's relevant to it. I'd go so far as to include a static method in your Server model, say 'retrieveServers', that returns an NSArray of Server objects.
That way, all your controller is doing is:
[self setNews:[Server retrieveServers]];
[_tableView reloadData];
Rather than having a lot of irrelevant code in your controller - this will increase maintainability and readability.
If you were so inclined, you could take another step, and provide custom accessors, rather than referencing the members directly via the model, for example:
Server *currentServer = nil;
if( self.news.count > indexPath.row ) {
currentServer = [_news objectAtIndex:indexPath.row];
}
[serverPlayers setText:(currentServer ? [currentServer getPlayers] : [Server defaultPlayerValue]]
The code above is being safe with checking that the array has at least the same number of elements in it as we need, and secondly, when assigning the value to the table cell (which may be re-used, and so needs to be set to sane values for every possible branch of execution). The advantages of doing the above: readability, centralised default value, maintainability.
I apologise if this seems rather overkill (TL;DR added ;/), trying to provide some pointers, which would aid in debugging if you see no other reason to employ the tips above.
Now I know what an NSRangeException is. What I dont get is why on the first run through of my code it's fine, but when the same code (and variables) are setup the second time it fails. Even stepping through the code I can't see where it is differing from the first run through.
The whole code gets a set of JSON values from my php api (basically just client information, units booked in, date, etc.) It used to just display it in a UITableView, I decided to group my table by the date it was booked in (Apple should have a nicer way to do this in my opinion, perhaps extending their UITableView?)
my .h
#interface LTBViewController : UIViewController {
IBOutlet UITableView *mainTableView;
NSArray *tickets; //for storing our serialized JSON
NSMutableData *data; //for appending our JSON as we're getting it
NSString *token; //for security authentication with our custom API
NSMutableArray *days; //for storing any days with bookings retrieved from tickets
NSMutableDictionary *groupedTickets; //for storing tickets to correlate to days
NSArray *filteredArray; //for our finalized output
}
#end
most of my .m (the important parts)
- (void)viewDidAppear:(BOOL)animated
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://myurl.com/api/api.php?api=%#&function=%#", token, #"active"]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
//[[NSURLConnection alloc] initWithRequest:request delegate:self]; old way of doing it
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//initialize from our settings file and make sure we're authenticated
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
token = [defaults objectForKey:#"api_token"];
NSLog(#"%#",token);
//initialize our sorting arrays
days = [[NSMutableArray alloc] init];
groupedTickets = [[NSMutableDictionary alloc] init];
filteredArray = [[NSArray alloc] init];
}
- (void)groupTable {
NSUInteger count = 0;
for (LTBViewController *ticket in tickets)
{
NSString *date = [[tickets objectAtIndex:count] objectForKey:#"dateIn"];
if (![days containsObject:date])
{
[days addObject:date];
[groupedTickets setObject:[NSMutableArray arrayWithObject:ticket] forKey:date];
}
else
{
[((NSMutableArray*)[groupedTickets objectForKey:date]) addObject:ticket];
}
count +=1;
}
count = 0;
[mainTableView reloadData];
}
//for when we get a response
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc] init];
}
//for getting data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)thedata
{
[data appendData:(thedata)];
}
//for when connection finished
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
tickets = [NSJSONSerialization JSONObjectWithData:data options:NO error:nil];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self groupTable]; //starts our grouping methods
}
//for when connection fails
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Could not connect to server" delegate:nil cancelButtonTitle:#"dismiss" otherButtonTitles:nil, nil];
[errorView show];
}
//overriding the "delete" text for our UITableView
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
return #"Close Ticket";
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *date = [days objectAtIndex:section];
return date;
}
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return [days count];
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *date = [days objectAtIndex:section];
NSLog(#"%#", date);
return [[groupedTickets objectForKey:date] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
tableView.layer.cornerRadius = 5.0; //rounds our table corners
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleSubtitle) reuseIdentifier:#"MainCell"];
}
//I'm sure theres a better way to do this but it works for now
NSString *date = [days objectAtIndex:[indexPath section]];
NSPredicate *search = [NSPredicate predicateWithFormat:#"dateIn == %#", date];
filteredArray = [tickets filteredArrayUsingPredicate:search];
//this right here is what fails the second time through at index 1
cell.textLabel.text = [NSString stringWithFormat:#"%#",[[filteredArray objectAtIndex:[indexPath row]] objectForKey:#"ticketnumber"] ];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", [[filteredArray objectAtIndex:[indexPath row]] objectForKey:#"customerName"]];
return cell;
}
When an item is clicked it moves to another view controller (that works alright) but when the back button is pressed from it is where this code fails. I would be appreciative of any extra sets of eyes going over it and helping me find my error. (or a better way of doing this)
Thanks!
Got it. I thought when I reassigned values to my NSMutableArray days and NSMutableDictionary groupedTickets it would just overwrite the previous values.
I performed release on view will disappear and it works now.
Still if there's a better way to do this I'd be interested in learning it.
Error creating row in tableview.
My table view is loaded with a json.
I saw many examples here, however none were able to solve
my code
NSMutableArray *myArray = [NSMutableArray arrayWithArray:news];
[myArray insertObject:#"teste" atIndex:0];
NSMutableArray *path = [NSMutableArray arrayWithObject:[NSIndexPath indexPathForRow:[news count]-1 inSection:1]];
[self.tableView insertRowsAtIndexPaths:path withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
error displayed
2013-05-30 23:15:17.345 lerJson[1141:c07] * Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:908
2013-05-30 23:15:17.347 lerJson[1141:c07] Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert row 11 into section 1, but there are only 1 sections after the update'
** First throw call stack:
(0x1c95012 0x10d2e7e 0x1c94e78 0xb68665 0xb670b 0xc4945 0xc4973 0x476d 0x76d78 0x789eb 0x2e185a 0x2e099b 0x2e20df 0x2e4d2d 0x2e4cac 0x2dca28 0x49972 0x49e53 0x27d4a 0x19698 0x1bf0df9 0x1bf0ad0 0x1c0abf5 0x1c0a962 0x1c3bbb6 0x1c3af44 0x1c3ae1b 0x1bef7e3 0x1bef668 0x16ffc 0x26ad 0x25d5)
libc++abi.dylib: terminate called throwing an exception
My full code j2_ViewController.h
#import "j2_ViewController.h"
#interface j2_ViewController ()
#end
#implementation j2_ViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self carregaDados];
}
-(void)carregaDados
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url2 = [[NSURL alloc] initWithString:[#"http://localhost:3000/json.aspx?ind=0&tot=12" stringByAddingPercentEscapesUsingEncoding:NSISOLatin1StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url2];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
-(void)carregaDados2
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url2 = [[NSURL alloc] initWithString:[#"http://localhost:3000/json.aspx?ind=12&tot=12" stringByAddingPercentEscapesUsingEncoding:NSISOLatin1StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url2];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSError *jsonParsingError = nil;
NSMutableData *myData = [[NSMutableData alloc]init];
[myData appendData:response];
NSMutableArray *news2;
news2 = [NSJSONSerialization JSONObjectWithData:myData options:nil error:&jsonParsingError];
//NSLog(#"LOG: %#", news2);
NSMutableArray *myArray = [NSMutableArray arrayWithArray:news];
[myArray addObjectsFromArray:news2];
NSLog(#"LOG: %#", myArray);
news = myArray;
//NSLog(#"Erro: %#", jsonParsingError);
[tableViewjson reloadData];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc]init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSError *e = nil;
news = [NSJSONSerialization JSONObjectWithData:data options:nil error:&e];
[tableViewjson reloadData];
//NSLog(#"erro=%#",e);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Erro" message:#"Não foi possivle fazer o download - cheque sua conexão 3g ou wi-fi" delegate:nil cancelButtonTitle:#"cancelar" otherButtonTitles:nil];
[alert show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSInteger teste = [news count];
return teste;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"celula";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [[news objectAtIndex:indexPath.row] objectForKey:#"nome"];
return cell;
}
//chama essa função quando o scroll atinge seu final
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSInteger currentOffset = scrollView.contentOffset.y;
NSInteger maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if(maximumOffset - currentOffset <= -40)
{
//carega mais dados
//[self carregaDados2];
[self.tableView beginUpdates];
NSMutableArray *myArray = [NSMutableArray arrayWithArray:news];
[myArray insertObject:#"teste" atIndex:0];
NSMutableArray *path = [NSMutableArray arrayWithObject:[NSIndexPath indexPathForRow:[news count]-1 inSection:1]];
[self.tableView insertRowsAtIndexPaths:path withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
}
#end
My delegation has not changed
You also need to update your datasource like this:
[news insertObject:#"teste" atIndex:0];
I assume you only have 1 section.
change this code: NSMutableArray *path = [NSMutableArray arrayWithObject:[NSIndexPath indexPathForRow:[news count] inSection:1]];
to this code: NSMutableArray *path = [NSMutableArray arrayWithObject:[NSIndexPath indexPathForRow:[news count] inSection:0]];
sections and rows starts at 0.