I am trying to pass data to labels from my JSON file onto a simple ViewController but I don't know where to actually pass that data. Would I be able to just add to my setDataToJson method or would I add the data in my viewDidLoad method?
here is my code
#interface NSDictionary(JSONCategories)
+(NSDictionary*)dictionaryWithContentsOfJSONString:(NSString*)fileLocation;
#end
#implementation NSDictionary(JSONCategories)
+(NSDictionary*)dictionaryWithContentsOfJSONString:(NSString*)fileLocation{
NSData* data = [NSData dataWithContentsOfFile:fileLocation];
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions error:&error];
if (error != nil) return nil;
return result;
}
#end
#implementation ViewController
#synthesize name;
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)setDataToJson{
NSDictionary *infomation = [NSDictionary dictionaryWithContentsOfJSONString:#"Test.json"];
name.text = [infomation objectForKey:#"AnimalName"];//does not pass data
}
The problem is the way you're trying to retrieve your file. In order to do it right, you should find first its path in the bundle. Try something like this:
+(NSDictionary*)dictionaryWithContentsOfJSONString:(NSString*)fileLocation{
NSString *filePath = [[NSBundle mainBundle] pathForResource:[fileLocation stringByDeletingPathExtension] ofType:[fileLocation pathExtension]];
NSData* data = [NSData dataWithContentsOfFile:filePath];
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions error:&error];
// Be careful here. You add this as a category to NSDictionary
// but you get an id back, which means that result
// might be an NSArray as well!
if (error != nil) return nil;
return result;
}
After doing that and once your view is loaded, you should be able to set your labels by retrieving the json like this:
-(void)setDataToJson{
NSDictionary *infomation = [NSDictionary dictionaryWithContentsOfJSONString:#"Test.json"];
self.name.text = [infomation objectForKey:#"AnimalName"];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setDataToJson];
}
It should be valueForKey instead.
Example:
name.text = [infomation valueForKey:#"AnimalName"];
Related
I'm still new to using NSURL to get data and seem to have issues whenever trying to use this. In this case I use debug to check all the date coming in in ViewDidload and all the correct data comes in and is split into the arrays I then want to use to build my table view controller. However when we reach the NumberOfRows in section method, all of the arrays seem to have been reset to nil.
I've tried using various combinations of NSURL solutions but none seem to get any further than the one I am using right now (which at least shows some data arrriving). Can anyone please let me know if I am making an obvious mistake, or if not give me a reliable piece of code which I should use to perform a simple GET like this.
Thank you very much.
Here below my code:
#implementation MyLessonsTableViewController
NSArray *pastarr = nil;
NSArray *todoarr = nil;
NSArray *comingarr = nil;
NSArray *jsonless = nil;
- (void)viewDidLoad {
[super viewDidLoad];
// GET MY LESSONS FROM DATABASE
jsonless = [[NSArray alloc] init];
pastarr = [[NSArray alloc] init];
todoarr = [[NSArray alloc] init];
comingarr = [[NSArray alloc] init];
NSString *token = #"5cfd28bed3f5f5bd63143c81a50d434a";
NSString *urlString = [NSString stringWithFormat:#"http://soon.nextdoorteacher.com/apps/api/nextdoorteacher/student-lessons?t=%#", token];
NSURL *urlcc = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:urlcc];
NSError *error;
NSMutableDictionary *jsonLess = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions
error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
NSLog(#"My Lessons Json == %#", jsonLess);
// SPLIT ARRAY
NSArray *pastarr = [jsonLess valueForKeyPath:#"past"];
NSArray *todoarr = [jsonLess valueForKeyPath:#"todo"];
NSArray *comingarr = [jsonLess valueForKeyPath:#"upcoming"];
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
NSUInteger lessonRowCount = 0;
switch (section) {
case 0:
lessonRowCount = todoarr.count;
break;
case 1:
lessonRowCount = comingarr.count;
break;
case 2:
lessonRowCount = pastarr.count;
break;
default:
break;
}
return lessonRowCount;
}
Several issues.
You call reloadData needlessly in dispatch_async.
You call reloadData before you process jsonLess.
You never assign anything to your array ivars.
You don't actually have ivars for your arrays. You have global variables.
Here's your posted code all fixed up:
#implementation MyLessonsTableViewController {
NSArray *pastarr = nil;
NSArray *todoarr = nil;
NSArray *comingarr = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
// GET MY LESSONS FROM DATABASE
NSString *token = #"5cfd28bed3f5f5bd63143c81a50d434a";
NSString *urlString = [NSString stringWithFormat:#"http://soon.nextdoorteacher.com/apps/api/nextdoorteacher/student-lessons?t=%#", token];
NSURL *urlcc = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:urlcc];
NSError *error;
NSMutableDictionary *jsonLess = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions
error:&error];
NSLog(#"My Lessons Json == %#", jsonLess);
// SPLIT ARRAY
pastarr = [jsonLess valueForKeyPath:#"past"];
todoarr = [jsonLess valueForKeyPath:#"todo"];
comingarr = [jsonLess valueForKeyPath:#"upcoming"];
[self.tableView reloadData];
}
Now this still suffers from one big problem. You are doing Internet access on the main thread. That's bad. You really should do it this way:
- (void)viewDidLoad {
[super viewDidLoad];
// GET MY LESSONS FROM DATABASE
NSString *token = #"5cfd28bed3f5f5bd63143c81a50d434a";
NSString *urlString = [NSString stringWithFormat:#"http://soon.nextdoorteacher.com/apps/api/nextdoorteacher/student-lessons?t=%#", token];
NSURL *urlcc = [NSURL URLWithString:urlString];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:urlcc];
NSError *error;
NSMutableDictionary *jsonLess = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions
error:&error];
NSLog(#"My Lessons Json == %#", jsonLess);
// SPLIT ARRAY
pastarr = [jsonLess valueForKeyPath:#"past"];
todoarr = [jsonLess valueForKeyPath:#"todo"];
comingarr = [jsonLess valueForKeyPath:#"upcoming"];
// Now this must be done on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}};
}
I have the following code and it is working to an extent :
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSString *strURL = [NSString stringWithFormat:#"http://localhost:8888/service.php"];
NSURL *url = [NSURL URLWithString:strURL];
NSData * data = [NSData dataWithContentsOfURL:url];
NSError * error;
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
AdObject *someAdObject = [[AdObject alloc] init];
//NSLog(#"%#", json);
self.detailLabel.text = self.tempString;
}
Now when the commented out NSLog actually prints the JSON dictionary, I get :
{
brand = "";
category = Games;
country = "Japan";
"discount_rate" = 50;
duration = 5;
id = 1;
"issue_date" = "2014-04-07";
location = "Heishi Mall";
title = "Gamestory videogames sales!";
user = "";
}
)
I created an Ad object which has properties such as title, location, country, etc (as reflected above). I would like to access the JSON above and store value in object variables.
You can access that values :-
for(NSDictionary *item in json) {
NSLog(#"%#",[item valueForKey:#"key"]);
someAdObject.key = [item valueForKey:#"key"];
}
Try this and review your json also
AdObject *someAdObject = nil;
for(NSDictionary *item in json) {
someAdObject = [[AdObject alloc] init];
someAdObject.category = [item valueForKey#"category"];
someAdObject.country = [item valueForKey#"country"];
someAdObject.discount_rate = [item valueForKey#"discount_rate"];
someAdObject.duration = [item valueForKey#"duration"];
//and same all of your required object properties
}
I have a json object:
#interface Order : NSObject
#property (nonatomic, retain) NSString *OrderId;
#property (nonatomic, retain) NSString *Title;
#property (nonatomic, retain) NSString *Weight;
- (NSMutableDictionary *)toNSDictionary;
...
- (NSMutableDictionary *)toNSDictionary
{
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setValue:self.OrderId forKey:#"OrderId"];
[dictionary setValue:self.Title forKey:#"Title"];
[dictionary setValue:self.Weight forKey:#"Weight"];
return dictionary;
}
In string this is:
{
"Title" : "test",
"Weight" : "32",
"OrderId" : "55"
}
I get string JSON with code:
NSMutableDictionary* str = [o toNSDictionary];
NSError *writeError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:str options:NSJSONWritingPrettyPrinted error:&writeError];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
Now I need to create and map object from JSON string:
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *e;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:&e];
This returns me filled NSDictionary.
What should I do to get object from this dictionary?
Add a new initWithDictionary: method to Order:
- (instancetype)initWithDictionary:(NSDictionary*)dictionary {
if (self = [super init]) {
self.OrderId = dictionary[#"OrderId"];
self.Title = dictionary[#"Title"];
self.Weight = dictionary[#"Weight"];
}
return self;
}
Don't forget to add initWithDictionary's signature to Order.h file
In the method where you get JSON:
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *e;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:&e];
Order *order = [[Order alloc] initWithDictionary:dict];
If the property names on your object match the keys in the JSON string you can do the following:
To map the JSON string to your Object you need to convert the string into a NSDictionary first and then you can use a method on NSObject that uses Key-Value Coding to set each property.
NSError *error = nil;
NSData *jsonData = ...; // e.g. [myJSONString dataUsingEncoding:NSUTF8Encoding];
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingOptionsAllowFragments error:&error];
MyObject *object = [[MyObject alloc] init];
[object setValuesForKeysWithDictionary:jsonDictionary];
If the keys do not match you can override the instance method of NSObject -[NSObject valueForUndefinedKey:] in your object class.
To map you Object to JSON you can use the Objective-C runtime to do it automatically. The following works with any NSObject subclass:
#import <objc/runtime.h>
- (NSDictionary *)dictionaryValue
{
NSMutableArray *propertyKeys = [NSMutableArray array];
Class currentClass = self.class;
while ([currentClass superclass]) { // avoid printing NSObject's attributes
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(currentClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if (propName) {
NSString *propertyName = [NSString stringWithUTF8String:propName];
[propertyKeys addObject:propertyName];
}
}
free(properties);
currentClass = [currentClass superclass];
}
return [self dictionaryWithValuesForKeys:propertyKeys];
}
Assuming that your properties names and the dictionary keys are the same, you can use this function to convert any object
- (void) setObject:(id) object ValuesFromDictionary:(NSDictionary *) dictionary
{
for (NSString *fieldName in dictionary) {
[object setValue:[dictionary objectForKey:fieldName] forKey:fieldName];
}
}
this will be more convenient for you :
- (instancetype)initWithDictionary:(NSDictionary*)dictionary {
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dictionary];}
return self;
}
The perfect way to do this is by using a library for serialization/deserialization
many libraries are available but one i like is
JagPropertyConverter
https://github.com/jagill/JAGPropertyConverter
it can convert your Custom object into NSDictionary and vice versa
even it support to convert dictionary or array or any custom object within your object (i.e Composition)
JAGPropertyConverter *converter = [[JAGPropertyConverter alloc]init];
converter.classesToConvert = [NSSet setWithObjects:[Order class], nil];
#interface Order : NSObject
#property (nonatomic, retain) NSString *OrderId;
#property (nonatomic, retain) NSString *Title;
#property (nonatomic, retain) NSString *Weight;
#end
//For Dictionary to Object (AS IN YOUR CASE)
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setValue:self.OrderId forKey:#"OrderId"];
[dictionary setValue:self.Title forKey:#"Title"];
[dictionary setValue:self.Weight forKey:#"Weight"];
Order *order = [[Order alloc]init];
[converter setPropertiesOf:order fromDictionary:dictionary];
//For Object to Dictionary
Order *order = [[Order alloc]init];
order.OrderId = #"10";
order.Title = #"Title;
order.Weight = #"Weight";
NSDictionary *dictPerson = [converter convertToDictionary:person];
Define your custom class inherits from "AutoBindObject". Declare properties which has the same name with keys in NSDictionary. Then call method:
[customObject loadFromDictionary:dic];
Actually, we can customize class to map different property names to keys in dictionary. Beside that, we can bind nested objects.
Please have a look to this demo. The usage is easy:
https://github.com/caohuuloc/AutoBindObject
im trying to show on a map two json files simulteniously. But there is the problem it gives me weird errors which I can't figure it out. So originaly there was
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray * annotations = [[NSMutableArray alloc] init];
self.mapView.visibleMapRect = MKMapRectMake(135888858.533591, 92250098.902419, 190858.927912, 145995.678292);
NSLog(#"Loading data…");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData * JSONData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:self.seedFileName ofType:#"json"]];
for (NSDictionary * annotationDictionary in [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:NULL])
{
ADClusterableAnnotation * annotation = [[ADClusterableAnnotation alloc] initWithDictionary:annotationDictionary];
[annotations addObject:annotation];
[annotation release];
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Building KD-Tree…");
[self.mapView setAnnotations:annotations];
});
});
[annotations release];
}
so i saw that i have two ViewControllers which holds the the seedfile
this one is
#import "CDStreetlightsMapViewController.h"
#implementation CDStreetlightsMapViewController
- (NSString *)pictoName {
return #"CDStreetlight.png";
}
- (NSString *)clusterPictoName {
return #"CDStreetlightCluster.png";
}
- (NSString *)seedFileName {
return #"CDStreetlights";
}
- (NSString *)seedFileName1 {
return #"CDToilets";
}
#end
the other one is
#import "CDToiletsMapViewController.h"
#implementation CDToiletsMapViewController
- (NSString *)seedFileName {
return #"CDToilets";
}
- (NSString *)pictoName {
return #"CDToilet.png";
}
- (NSString *)clusterPictoName {
return #"CDToiletCluster.png";
}
#end
The json files are named CDToilets and CDStreetlights... but i have a tab bar which holds Toilets and streetlights. But lets say i would like to display on the toilets viewController the streetlights and the toilets? thats my problem right know.. I tried this
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray * annotations = [[NSMutableArray alloc] init];
self.mapView.visibleMapRect = MKMapRectMake(135888858.533591, 92250098.902419, 190858.927912, 145995.678292);
NSLog(#"Loading data…");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData * JSONData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:self.seedFileName ofType:#"json"]];
for (NSDictionary * annotationDictionary in [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:NULL])
{
ADClusterableAnnotation * annotation = [[ADClusterableAnnotation alloc] initWithDictionary:annotationDictionary];
[annotations addObject:annotation];
[annotation release];
}
NSData * JSONData1 = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:self.seedFileName1 ofType:#"json"]];
for (NSDictionary * annotationDictionary in [NSJSONSerialization JSONObjectWithData:JSONData1 options:kNilOptions error:NULL])
{
ADClusterableAnnotation * annotation = [[ADClusterableAnnotation alloc] initWithDictionary:annotationDictionary];
[annotations addObject:annotation];
[annotation release];
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Building KD-Tree…");
[self.mapView setAnnotations:annotations];
});
});
[annotations release];
}
i renamed in the CDStreetlightsMapViewController the seedfile as seedfile1 so i can use it two times in viewDidLoad it didn't show errors but it didn't run well on simulator and I get an exception:
ClusterDemo[11014:c07]Loading data…
ClusterDemo[11014:1303]***Assertion failure in -[CDToiletsMapViewController seedFileName1], ADClusterMapView-master-7/ClusterDemo/Classes/CDMapViewController.m:86 2013-11-25 23:59:37.524
ClusterDemo[11014:1303]***Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This abstract method must be overridden!' *** First throw call stack: (0x18b2012 0x1277e7e 0x18b1e78 0xd0df35 0x3fad 0x36cb 0x2deb53f 0x2dfd014 0x2dee2e8 0x2dee450 0x99843e72 0x9982bdaa) libc++abi.dylib: terminate called throwing an exception (lldb)
you can download the full app here... https://github.com/applidium/ADClusterMapView
If you read the exception it's quite clear that you're using an abstract method that contains an assertion to ensure that you know you've made a mistake. You need to implement the seedFileName1 method in CDToiletsMapViewController (just like you did in CDStreetlightsMapViewController).
From the code I guess you need to add:
- (NSString *)seedFileName1 {
return #"CDStreetlights";
}
First of all, excuse me for my bad english but I'm french and I'll try my best to be understandable.
So, I'm coding a simple application with this structure :
- viewController class (deal with the UI)
- product class (define the object product)
- ws_product class (contains some functions which get json datas)
What I'm trying to do is to return the products array, that I get after I parsed my json in ws_product, in my viewController. Thanks to this I'll can fill my tableView and my application will no longer be empty !
My actual ws_product is :
#import "WS_Produit.h"
#import "Produit.h"
#import "ViewController.h"
#implementation WS_Produit
- (NSMutableArray *)getProduitsJSON
{
__block NSMutableArray *result;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
NSLog(#"on passe en async");
NSError *error = nil;
NSData *jsonData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"the url to load"]];
NSDictionary *produits = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if( error )
{
NSLog(#"%#", [error localizedDescription]);
}
else {
dispatch_sync(dispatch_get_main_queue(), ^(){
NSLog(#"retour en sync");
result = [[NSMutableArray alloc] init];
Produit *tmp;
NSArray *produit = produits[#"produits"];
for ( NSDictionary *property in produit )
{
tmp = [Produit new];
tmp.ref = property[#"ref"];
tmp.name = property[#"name"];
tmp.description = property[#"description"];
tmp.price = property[#"price"];
tmp.imgURL = property[#"imgURL"];
[result addObject:tmp];
NSLog(#"%#", result);
}
});
}
});
NSLog(#"sortie du block");
NSLog(#"%#", result);
return result;
}
#end
My problem is when I'm out of the dispatch_queue my result array is empty so it's useless to return it in my viewController class, what can I do ?
Because you're using dispatch_async, your results array will be returned as empty before it gets filled.
Blocks are exactly what you need. They can be used as callbacks for async methods.
In your viewController, you should pass blocks to your method
[myObject getProduitsJSON:
success:^(NSArray *results){
// Use your results here
// Reload table for example.
}
failure:^(NSError *error){
// Use your error message (show it for example)
}];
So you're method should look like this:
-(void)getProduitsJson:(void(^)(NSArray* results))success failure:(void(^)(NSError* error))failure {
{
NSMutableArray *result = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
NSError *error = nil;
NSData *jsonData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"the url to load"]];
NSDictionary *produits = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if(error) {
failure(error);
}else{
// Fill your array
success(result);
}
}
}