UITableView not loading but array is populated - ios

I have a UITableView that I would like to populate with data via a WebAPI. I have everything set up and I can verify that data is returned in JSON format. What I don't understand is why I see that data in my array printed out several times when I use NSLog and also how do I assign this data to the UITableView. I was able to populate the table from the viewDidLoad method but these are hard-coded values. I want to populate the grid with data that is returned from my call to the remote server. This data is available to me in my didReceiveData delegate. What am I doing wrong here?
I have this code in my MasterViewControler.h
#interface MasterViewController : UITableViewController <UITableViewDataSource, UIAlertViewDelegate> {
NSMutableArray *dataArray;
NSMutableArray *categoryNames;
}
Here is an abridged version of what I have in my MasterViewController.m file
NSMutableData* receivedData;
NSString* hostName;
NSInteger portNumber = 9999;
NSMutableDictionary* dictionary;
NSInteger maxRetryCount = 5;
int count = 0;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"Succeeded! Received %d bytes of data",[data length]);
NSError *error = nil;
// Get the JSON data from the website
id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if ([result isKindOfClass:[NSArray class]]) {
for (NSArray *item in result)
[dataArray addObject:item];
NSLog(#"%#", dataArray);
}
else {
NSDictionary *jsonDictionary = (NSDictionary *)result;
for(NSDictionary *item in jsonDictionary)
NSLog(#"Item: %#", item);
}}
- (void)viewDidLoad{
[super viewDidLoad];
hostName = [[NSString alloc] initWithString:#"12.34.56.78"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://%#:%i%#", hostName, portNumber, #"/api/products"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url cachePolicy: NSURLRequestReloadIgnoringLocalCacheData timeoutInterval: 2];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addNewItem)]; //insertNewObject
self.navigationItem.rightBarButtonItem = addButton;
dataArray = [[NSMutableArray alloc] init];
//[dataArray addObject:#"Apple"];
//[dataArray addObject:#"Mango"];
//[dataArray addObject:#"Orange"];}
Instead of populating the dataArray here I was trying to populate it in the didReceiveData delegate. The dataArray will be allocated but it is almost like I have to reload the UITableView to see the values. I tried that at the end of didReceiveData but I received an error.

First make sure your UITableViewDataSource and UITableViewDelegate are set.
Once the array is populated (in your case, at the end of -(void)connection:didReceiveData:) you'd call [tableView reloadData] to refresh the table.
Then you'd set your cell in:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// If your array is an array of strings, change if applicable
NSString *string = [dataArray objectAtIndex:indexPath.row];
[cell.textLabel setText:string];
}

Your class should implement UITableView's UITableViewDataSource protocol, and then assign itself as the dataSource. Then implement cellForRowAtIndexPath:, which is where you actually use your model array to initialize the cells. When your data is ready, call reloadData on the table view.

Related

reloadData is stacking ontop of old data

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.

AFNetworking EXC_BAD_ACCESS (code=2, address=0x0) in downloadTask

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.

Xcode NSRangeException on second run through of code

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.

xcode get link json format from server api [duplicate]

I have a JSON string (from PHP's json_encode() that looks like this:
[{"id": "1", "name":"Aaa"}, {"id": "2", "name":"Bbb"}]
I want to parse this into some sort of data structure for my iPhone app. I guess the best thing for me would be to have an array of dictionaries, so the 0th element in the array is a dictionary with keys "id" => "1" and "name" => "Aaa".
I do not understand how the NSJSONSerialization stores the data though. Here is my code so far:
NSError *e = nil;
NSDictionary *JSON = [NSJSONSerialization
JSONObjectWithData: data
options: NSJSONReadingMutableContainers
error: &e];
This is just something I saw as an example on another website. I have been trying to get a read on the JSON object by printing out the number of elements and things like that, but I am always getting EXC_BAD_ACCESS.
How do I use NSJSONSerialization to parse the JSON above, and turn it into the data structure I mentioned?
Your root json object is not a dictionary but an array:
[{"id": "1", "name":"Aaa"}, {"id": "2", "name":"Bbb"}]
This might give you a clear picture of how to handle it:
NSError *e = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableContainers error: &e];
if (!jsonArray) {
NSLog(#"Error parsing JSON: %#", e);
} else {
for(NSDictionary *item in jsonArray) {
NSLog(#"Item: %#", item);
}
}
This is my code for checking if the received json is an array or dictionary:
NSError *jsonError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&jsonError];
if ([jsonObject isKindOfClass:[NSArray class]]) {
NSLog(#"its an array!");
NSArray *jsonArray = (NSArray *)jsonObject;
NSLog(#"jsonArray - %#",jsonArray);
}
else {
NSLog(#"its probably a dictionary");
NSDictionary *jsonDictionary = (NSDictionary *)jsonObject;
NSLog(#"jsonDictionary - %#",jsonDictionary);
}
I have tried this for options:kNilOptions and NSJSONReadingMutableContainers and works correctly for both.
Obviously, the actual code cannot be this way where I create the NSArray or NSDictionary pointer within the if-else block.
It works for me. Your data object is probably nil and, as rckoenes noted, the root object should be a (mutable) array. See this code:
NSString *jsonString = #"[{\"id\": \"1\", \"name\":\"Aaa\"}, {\"id\": \"2\", \"name\":\"Bbb\"}]";
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *e = nil;
NSMutableArray *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&e];
NSLog(#"%#", json);
(I had to escape the quotes in the JSON string with backslashes.)
Your code seems fine except the result is an NSArray, not an NSDictionary, here is an example:
The first two lines just creates a data object with the JSON, the same as you would get reading it from the net.
NSString *jsonString = #"[{\"id\": \"1\", \"name\":\"Aaa\"}, {\"id\": \"2\", \"name\":\"Bbb\"}]";
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *e;
NSMutableArray *jsonList = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&e];
NSLog(#"jsonList: %#", jsonList);
NSLog contents (a list of dictionaries):
jsonList: (
{
id = 1;
name = Aaa;
},
{
id = 2;
name = Bbb;
}
)
[{"id": "1", "name":"Aaa"}, {"id": "2", "name":"Bbb"}]
In above JSON data, you are showing that we have an array contaning the number of dictionaries.
You need to use this code for parsing it:
NSError *e = nil;
NSArray *JSONarray = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableContainers error: &e];
for(int i=0;i<[JSONarray count];i++)
{
NSLog(#"%#",[[JSONarray objectAtIndex:i]objectForKey:#"id"]);
NSLog(#"%#",[[JSONarray objectAtIndex:i]objectForKey:#"name"]);
}
For swift 3/3+
//Pass The response data & get the Array
let jsonData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [AnyObject]
print(jsonData)
// considering we are going to get array of dictionary from url
for item in jsonData {
let dictInfo = item as! [String:AnyObject]
print(dictInfo["id"])
print(dictInfo["name"])
}
The following code fetches a JSON object from a webserver, and parses it to an NSDictionary. I have used the openweathermap API that returns a simple JSON response for this example. For keeping it simple, this code uses synchronous requests.
NSString *urlString = #"http://api.openweathermap.org/data/2.5/weather?q=London,uk"; // The Openweathermap JSON responder
NSURL *url = [[NSURL alloc]initWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSData *GETReply = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:GETReply options:NSJSONReadingMutableLeaves|| NSJSONReadingMutableContainers error:nil];
Nslog(#"%#",res);
#rckoenes already showed you how to correctly get your data from the JSON string.
To the question you asked: EXC_BAD_ACCESS almost always comes when you try to access an object after it has been [auto-]released. This is not specific to JSON [de-]serialization but, rather, just has to do with you getting an object and then accessing it after it's been released. The fact that it came via JSON doesn't matter.
There are many-many pages describing how to debug this -- you want to Google (or SO) obj-c zombie objects and, in particular, NSZombieEnabled, which will prove invaluable to you in helping determine the source of your zombie objects. ("Zombie" is what it's called when you release an object but keep a pointer to it and try to reference it later.)
Swift 2.0 on Xcode 7 (Beta) with do/try/catch block:
// MARK: NSURLConnectionDataDelegate
func connectionDidFinishLoading(connection:NSURLConnection) {
do {
if let response:NSDictionary = try NSJSONSerialization.JSONObjectWithData(receivedData, options:NSJSONReadingOptions.MutableContainers) as? Dictionary<String, AnyObject> {
print(response)
} else {
print("Failed...")
}
} catch let serializationError as NSError {
print(serializationError)
}
}
NOTE: For Swift 3.
Your JSON String is returning Array instead of Dictionary. Please try out the following:
//Your JSON String to be parsed
let jsonString = "[{\"id\": \"1\", \"name\":\"Aaa\"}, {\"id\": \"2\", \"name\":\"Bbb\"}]";
//Converting Json String to NSData
let data = jsonString.data(using: .utf8)
do {
//Parsing data & get the Array
let jsonData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [AnyObject]
//Print the whole array object
print(jsonData)
//Get the first object of the Array
let firstPerson = jsonData[0] as! [String:Any]
//Looping the (key,value) of first object
for (key, value) in firstPerson {
//Print the (key,value)
print("\(key) - \(value) ")
}
} catch let error as NSError {
//Print the error
print(error)
}
#import "homeViewController.h"
#import "detailViewController.h"
#interface homeViewController ()
#end
#implementation homeViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.frame = CGRectMake(0, 20, 320, 548);
self.title=#"Jason Assignment";
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
[self clientServerCommunication];
}
-(void)clientServerCommunication
{
NSURL *url = [NSURL URLWithString:#"http://182.72.122.106/iphonetest/getTheData.php"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:req delegate:self];
if (connection)
{
webData = [[NSMutableData alloc]init];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[webData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[webData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
/*Third party API
NSString *respStr = [[NSString alloc]initWithData:webData encoding:NSUTF8StringEncoding];
SBJsonParser *objSBJson = [[SBJsonParser alloc]init];
NSDictionary *responseDict = [objSBJson objectWithString:respStr]; */
resultArray = [[NSArray alloc]initWithArray:[responseDict valueForKey:#"result"]];
NSLog(#"resultArray: %#",resultArray);
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
//#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//#warning Incomplete method implementation.
// Return the number of rows in the section.
return [resultArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// Configure the cell...
cell.textLabel.text = [[resultArray objectAtIndex:indexPath.row] valueForKey:#"name"];
cell.detailTextLabel.text = [[resultArray objectAtIndex:indexPath.row] valueForKey:#"designation"];
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[resultArray objectAtIndex:indexPath.row] valueForKey:#"image"]]];
cell.imageview.image = [UIImage imageWithData:imageData];
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark - Table view delegate
// In a xib-based application, navigation from a table can be handled in -tableView:didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here, for example:
//Create the next view controller.
detailViewController *detailViewController1 = [[detailViewController alloc]initWithNibName:#"detailViewController" bundle:nil];
//detailViewController *detailViewController = [[detailViewController alloc] initWithNibName:#"detailViewController" bundle:nil];
// Pass the selected object to the new view controller.
// Push the view controller.
detailViewController1.nextDict = [[NSDictionary alloc]initWithDictionary:[resultArray objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:detailViewController1 animated:YES];
// Pass the selected object to the new view controller.
// Push the view controller.
// [self.navigationController pushViewController:detailViewController animated:YES];
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
empName.text=[nextDict valueForKey:#"name"];
deptlbl.text=[nextDict valueForKey:#"department"];
designationLbl.text=[nextDict valueForKey:#"designation"];
idLbl.text=[nextDict valueForKey:#"id"];
salaryLbl.text=[nextDict valueForKey:#"salary"];
NSString *ImageURL = [nextDict valueForKey:#"image"];
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageURL]];
image.image = [UIImage imageWithData:imageData];
}
The issue seems to be with autorelease of objects. NSJSONSerialization JSONObjectWithData is obviously creating some autoreleased objects and passing it back to you. If you try to take that on to a different thread, it will not work since it cannot be deallocated on a different thread.
Trick might be to try doing a mutable copy of that dictionary or array and use it.
NSError *e = nil;
id jsonObject = [NSJSONSerialization
JSONObjectWithData: data
options: NSJSONReadingMutableContainers
error: &e] mutableCopy];
Treating a NSDictionary as NSArray will not result in Bad access exception but instead will probably crash when a method call is made.
Also, may be the options do not really matter here but it is better to give NSJSONReadingMutableContainers | NSJSONReadingMutableContainers |
NSJSONReadingAllowFragments but even if they are autoreleased objects it may not solve this issue.
bad example, should be something like this
{"id":1, "name":"something as name"}
number and string are mixed.

Unable to reload UITableView after connectionDidFinishLoading completes

I am downloading and parsing JSON objects to build a "news feed" to populate a UITableView. The very last line of code I have in the connectionDidFinishLoading delegate method is:
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
However, my break points in the - (UITableViewCell *)tableView:(UITableView *)mytableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method are not hit. (They are hit when the app first launches)
So for whatever reason even though I am calling the reloadData on the main thread; it doesn't appear to be firing. I tried just [tableView reloadData] and that did not work.
Here is my connectionDidFinishLoading method:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSArray *publicTimeline = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
NSUInteger newsStreamCount = [publicTimeline count];
// Initialize the collection and the dictionary
newsItemManager.newsItemCollection = [[NSMutableArray alloc] initWithCapacity:newsStreamCount];
NSMutableArray *dictOfNewsItems = [[NSMutableArray alloc] initWithCapacity:newsStreamCount];
// From the JSON object, parse out the individual news item JSON objects and
// put them into a dictionary.
for (int i = 0; i < newsStreamCount; i++)
{
NSDictionary *item = [publicTimeline objectAtIndex:i];
[dictOfNewsItems addObject:item];
}
// For each news item JSON object, extract out the information we need for our
// news manager class
for (int i = 0; i < newsStreamCount; i++)
{
NSString *userName = [[dictOfNewsItems objectAtIndex:i] valueForKey:#"Title"];
NSString *message = [[dictOfNewsItems objectAtIndex:i] valueForKey:#"Content"];
NSString *imgUrl = [[dictOfNewsItems objectAtIndex:i] valueForKey:#"https://si0.twimg.com/logo_normal.jpg"];
NewsItem *newsItem = [[NewsItem alloc] initWithBasicInfo:userName :message :imgUrl];
[newsItemManager.newsItemCollection addObject:newsItem];
}
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
Here is my header file:
#import <UIKit/UIKit.h>
#interface NewsViewController : UITableViewController
#property (nonatomic, retain) NSMutableArray* newsItemArray;
#property (nonatomic, retain) NSMutableData *responseData;
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#end
Here is my implementation:
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.title = #"News";
//self.newsItemArray = [[NSMutableArray alloc] initWithObjects:#"One", #"Two", #"Three", nil];
tableView.rowHeight = 75.0;
responseData = [NSMutableData data];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://blah.com/api/news"]];
(void)[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
Thanks,
Flea
If your connectionDidFinish... method is inside your tableViewController class, maybe you just need
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
The overall problem was my newsItemManager.newsItemCollection was not being initialized properly and was returning null the entire time, thus when the UITableView was trying to load data; there was nothing to load.
I thought I had checked for this but one of those problems of staring at the computer all day and missing the obvious.

Resources