Mapping NSDictionary from JSON as a result of fetch HTTP request - objective-c

I have NSDictionary object as result of NSJSONSerialization, and it has child that is array of dictionary called list. Look at my json result:
{
"tags": [
"rofl",
"lmao",
"funny",
"haha",
"lmfao"
],
"result_type": "exact",
"list": [
{
"defid": 3689813,
"word": "Lol",
"author": "Lol B",
"permalink": "http://lol.urbanup.com/3689813",
"definition": "The name 'Lol' is an abreviated form of the name '[Laurence]'.",
"example": "\"Hey Lol, you alright?\"\r\n\r\n\"..Well i was chattin to Lol and he said..\"",
"thumbs_up": 44617,
"thumbs_down": 9926,
"current_vote": ""
},
------- bla bla bla ----------
],
"sounds": [
"http://media.urbandictionary.com/sound/lol-871.mp3",
------- bla bla bla ----------
]
}
I also created a class model for item (child of list).
#import <Foundation/Foundation.h>
#interface Item : NSObject
#property (strong, nonatomic) NSString *defid;
#property (strong, nonatomic) NSString *word;
#property (strong, nonatomic) NSString *author;
#property (strong, nonatomic) NSURL *permalink;
#property (strong, nonatomic) NSString *definition;
#property (strong, nonatomic) NSString *example;
-(instancetype)initWithDictionary:(NSDictionary *)dict;
#end
I also created its initializer
-(instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [super init]) {
self.defid = [dict valueForKey:#"defid"];
self.word = [dict valueForKey:#"word"];
self.author = [dict valueForKey:#"author"];
self.permalink = [dict valueForKey:#"permalink"];
self.definition = [dict valueForKey:#"definition"];
self.example = [dict valueForKey:#"example"];
}
return self;
}
I already create an array of list item (NSDictionary), my problem is I want to map those into my class so I can create an instance of list item. So how to convert item in array into my item class?
Should I iterate each item of array and call that initialization?
or any elegant method for doing that?
Additional question
I am relatively new into iOS development. I just want to parse my request result. Is there any tips for fetch some request and assign its result for datasource a table view (array)?
This is my fetch method:
- (void)fetchDataFromMashape:(NSURL *)URL {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"GET"];
[request setValue:API_KEY_MASHAPE forHTTPHeaderField:API_MASHAPE_HEADER_1];
[request setValue:API_ACCEPT forHTTPHeaderField:API_MASHAPE_HEADER_2];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Result: %#", jsonResult);
_results = [[NSArray alloc] initWithArray:[jsonResult valueForKey:#"list"]];
}];
[task resume];
}

My advice is to create a static method with the json list as a parameter
+ (NSArray<Item*>*) parseJson:(NSArray*) json {
NSMutableArray<Item*>* results = [[NSMutableArray alloc] initWithCapacity:son.length];
for (NSDictionary* anItem in json){
[results append:[[Item alloc] initWithDictionary:anItem]];
}
return results;
}

Related

How to pass NSArray from an NSObject class to a UIViewController class?

I am new to Objective-C. I am trying to create a weather app where I parsed data from open weather map. I have stored the parsed data to an array. Now want to access the array value from other class but getting null value.
Can anyone help me?
What I have tried:
Here is my NSObject class where I am storing data and trying to send that to view controller:
- (void)getCurrentWeather:(NSString *)query
{
NSString *const BASE_URL_STRING = #"http://api.openweathermap.org/data/2.5/weather?q=";
NSString *const API_KEY = #"&APPID=APIKEYSTRING";
NSString *weatherURLText = [NSString stringWithFormat:#"%#%#%#",
BASE_URL_STRING, query,API_KEY];
NSURL *weatherURL = [NSURL URLWithString:weatherURLText];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:weatherURL];
[self performSelectorOnMainThread:#selector(fetchedDataSmile | :) withObject:data waitUntilDone:YES];
});
}
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSString* cityName = [json objectForKey:#"name"];
int currentTempCelsius = (int)[[[json objectForKey:#"main"] objectForKey:#"temp"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int maxTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_max"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int minTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_min"] intValue] - ZERO_CELSIUS_IN_KELVIN;
NSString *weatherDescription = [[[json objectForKey:#"weather"] objectAtIndexBlush | :O ] objectForKey:#"description"];
weatherArray = [[NSMutableArray alloc] initWithObjects:cityName, weatherDescription,
[NSString stringWithFormat:#"%d", currentTempCelsius],
[NSString stringWithFormat:#"%d", maxTemp],
[NSString stringWithFormat:#"%d", minTemp],nil];
I have NSObject.h file as:
#interface WeatherData : NSObject
#property (nonatomic) NSString *weatherDescription;
#property (strong, nonatomic) NSString *currentTemp;
#property (nonatomic) int maxTempCelsius;
#property (nonatomic) int minTempCelsius;
#property (nonatomic, retain) NSMutableArray *weatherArray;
- (void)getCurrentWeather:(NSString *)query;
#end
In my view controller:
.h file:
#property (nonatomic, retain) NSMutableArray *weatherResultArray;
.m file:
-(void)searchButtonClicked:(UIButton*)sender
{
[self.view endEditing:YES];
WeatherData *weather = [[WeatherData alloc] init];
[weather getCurrentWeather:_textField.text];
self.weatherResultArray = weather.weatherArray;
//temperatureLabel.text = [NSString stringWithFormat:#"%d°",weather.currentTempCelsius];
}
I just want to show the results in UILabel.
Have you tried returning NSMutable array in this method
- (NSMutableArray*)getCurrentWeather:(NSString *)query
instead of this,
- (void)getCurrentWeather:(NSString *)query
This would be the easiest way to verify and also value can be retrieved in single statement as:
self.weatherResultArray = [weather getCurrentWeather:_textField.text];
One more thing, Don't forget to allocate and initialise your weatherResultArray as:
self.weatherResultArray = [[NSMutableArray alloc]init];
In NSObject class, define a weather protocol.
//NSObject.h file
#protocol WeatherDelegate<NSObject>
-(void)getWeatherData:(YourNSObjectClass*)viewController getWeatherData:(NSMutableArray*)array;
#end
//NSObject.m file, in
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSString* cityName = [json objectForKey:#"name"];
int currentTempCelsius = (int)[[[json objectForKey:#"main"] objectForKey:#"temp"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int maxTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_max"] intValue] - ZERO_CELSIUS_IN_KELVIN;
int minTemp = (int)[[[json objectForKey:#"main"] objectForKey:#"temp_min"] intValue] - ZERO_CELSIUS_IN_KELVIN;
NSString *weatherDescription = [[[json objectForKey:#"weather"] objectAtIndexBlush | :O ] objectForKey:#"description"];
weatherArray = [[NSMutableArray alloc] initWithObjects:cityName, weatherDescription,
[NSString stringWithFormat:#"%d", currentTempCelsius],
[NSString stringWithFormat:#"%d", maxTemp],
[NSString stringWithFormat:#"%d", minTemp],nil];
id<WeatherDelegate> strongDelegate = self.delegate;
if ([strongDelegate respondsToSelector:#selector(getWeatherData:getWeatherData:)])
{
[strongDelegate getWeatherData:self getWeatherData:weatherArray];
}
}
In yourViewController class,Add this WeatherData protocol and add the delegate function in .m file to fetch the data.
#interface yourViewControllerClass()<WeatherDelegate>
{
YourNSObjectClass *nsClass;
NSMutableArray *dataArray;
}
-(void)getWeatherData:(YourNSObjectClass*)viewController getWeatherData:(NSMutableArray*)array{
dataArray = [[NSMutableArray alloc]initWithArray:array];
}
-(void)searchButtonClicked:(UIButton*)sender
{
[self.view endEditing:YES];
WeatherData *weather = [[WeatherData alloc] init];
[weather getCurrentWeather:_textField.text];
self.weatherResultArray = dataArray;
//temperatureLabel.text = [NSString stringWithFormat:#"%d°",weather.currentTempCelsius];
}

Converting NSObject to NSDictionary

Hello I a class of type NSObject:
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
I want to pass the "details" object to a dictionary.
I did,
NSDictionary *dict = [NSDictionary dictionaryWithObject:details forKey:#"details"];
I am passing this dict to another method which performs a check on JSONSerialization:
if(![NSJSONSerialization isValidJSONObject:dict])
And I am getting a crash on this check. Am I doing anything wrong here? I know that the details I am getting is a JSON object and I am assigning it to the properties in my ProductDetails class.
Please help me. I am a noob in Objective-C.
I now tried:
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:(NSData*)details options:kNilOptions error:&error];
All I need here is an easy way to convert details to NSData.
I noticed that I have an array inside my object may be thats why all the ways I tried is throwing an exception. However since this question is becoming to big, I have started an another question thread for it where I have displayed the data I am getting inside the object - https://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary
This may well be the easiest way to achieve it. Do import #import <objc/runtime.h> in your class file.
#import <objc/runtime.h>
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
NSDictionary *dict = [self dictionaryWithPropertiesOfObject: details];
NSLog(#"%#", dict);
//Add this utility method in your class.
- (NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
NSDictionary *details = {#"name":product.name,#"color":product.color,#"quantity":#(product.quantity)};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:details
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
Second part's source: Generate JSON string from NSDictionary in iOS
As mmackh said, you want to define a custom method for your ProductDetails object that will return a simple NSDictionary of values, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity)};
}
...
Let's assume that we added manufacturer property to our ProductDetails, which referenced a ManufacturerDetails class. We'd just write a jsonObject for that class, too:
#implementation ManufacturerDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"address1" : self.address1,
#"address2" : self.address2,
#"city" : self.city,
...
#"phone" : self.phone};
}
...
And then change the jsonObject for ProductDetails to employ that, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity),
#"manufacturer" : [self.manufacturer jsonObject]};
}
...
If you have potentially nested collection objects (arrays and/or dictionaries) with custom objects that you want to encode, you could write a jsonObject method for each of those, too:
#interface NSDictionary (JsonObject)
- (id)jsonObject;
#end
#implementation NSDictionary (JsonObject)
- (id)jsonObject
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[dictionary setObject:[obj jsonObject] forKey:key];
else
[dictionary setObject:obj forKey:key];
}];
return [NSDictionary dictionaryWithDictionary:dictionary];
}
#end
#interface NSArray (JsonObject)
- (id)jsonObject;
#end
#implementation NSArray (JsonObject)
- (id)jsonObject
{
NSMutableArray *array = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[array addObject:[obj jsonObject]];
else
[array addObject:obj];
}];
return [NSArray arrayWithArray:array];
}
#end
If you do something like that, you can now convert arrays or dictionaries of your custom objects object into something that can be used for generating JSON:
NSArray *products = #[[[Product alloc] initWithName:#"Prius" color:#"Green" quantity:3],
[[Product alloc] initWithName:#"Accord" color:#"Black" quantity:1],
[[Product alloc] initWithName:#"Civic" color:#"Blue" quantity:2]];
id productsJsonObject = [products jsonObject];
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:productsJsonObject options:0 error:&error];
If you're simply trying to save these objects in a file, I'd suggest NSKeyedArchiver and NSKeyedUnarchiver. But if you need to generate JSON objects for your own private classes, you can do something like the above might work.
In .h File
#import <Foundation/Foundation.h>
#interface ContactDetail : NSObject
#property (nonatomic) NSString *firstName;
#property (nonatomic) NSString *lastName;
#property (nonatomic) NSString *fullName;
#property (nonatomic) NSMutableArray *mobileNumbers;
#property (nonatomic) NSMutableArray *Emails;
#property (assign) bool Isopen;
#property (assign) bool IsChecked;
-(NSDictionary *)dictionary;
#end
in .m file
#import "ContactDetail.h"
#import <objc/runtime.h>
#implementation ContactDetail
#synthesize firstName;
#synthesize lastName;
#synthesize fullName;
#synthesize mobileNumbers;
#synthesize Emails;
#synthesize IsChecked,Isopen;
//-(NSDictionary *)dictionary {
// return [NSDictionary dictionaryWithObjectsAndKeys:self.fullName,#"fullname",self.mobileNumbers,#"mobileNumbers",self.Emails,#"emails", nil];
//}
- (NSDictionary *)dictionary {
unsigned int count = 0;
NSMutableDictionary *dictionary = [NSMutableDictionary new];
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id value = [self valueForKey:key];
if (value == nil) {
// nothing todo
}
else if ([value isKindOfClass:[NSNumber class]]
|| [value isKindOfClass:[NSString class]]
|| [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSMutableArray class]]) {
// TODO: extend to other types
[dictionary setObject:value forKey:key];
}
else if ([value isKindOfClass:[NSObject class]]) {
[dictionary setObject:[value dictionary] forKey:key];
}
else {
NSLog(#"Invalid type for %# (%#)", NSStringFromClass([self class]), key);
}
}
free(properties);
return dictionary;
}
#end
if any crash ,You check the property (NSMutableArray,NSString,etc ) in else if condition inside of for.
In Your Controller, in any func...
-(void)addItemViewController:(ConatctViewController *)controller didFinishEnteringItem:(NSMutableArray *)SelectedContact
{
NSLog(#"%#",SelectedContact);
NSMutableArray *myData = [[NSMutableArray alloc] init];
for (ContactDetail *cont in SelectedContact) {
[myData addObject:[cont dictionary]];
}
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myData options:NSJSONWritingPrettyPrinted error:&error];
if ([jsonData length] > 0 &&
error == nil){
// NSLog(#"Successfully serialized the dictionary into data = %#", jsonData);
NSString *jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSLog(#"JSON String = %#", jsonString);
}
else if ([jsonData length] == 0 &&
error == nil){
NSLog(#"No data was returned after serialization.");
}
else if (error != nil){
NSLog(#"An error happened = %#", error);
}
}
Try this:
#import <objc/runtime.h>
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] ? [obj valueForKey:key] : #"" forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
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:[ProductDetails class], nil];
//For Object to Dictionary
NSDictionary *dictDetail = [converter convertToDictionary:detail];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:dictDetail options:NSJSONWritingPrettyPrinted error:&error];
You can convert object (say modelObject) to dictionary at runtime with the help of objc/runtime.h class but that has certain limitations and is not recommended.
Considering MVC, mapping logic should be implemented in Model class.
#interface ModelObject : NSObject
#property (nonatomic) NSString *p1;
#property (nonatomic) NSString *p2;
-(NSDictionary *)dictionary;
#end
#import "ModelObject.h"
#implementation ModelObject
-(NSDictionary *)dictionary
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:self.p1 forKey:#"p1"];// you can give different key name here if you want
[dict setValue:self.p2 forKey:#"p2" ];
return dict;
}
#end
Uses:
NSDictionary *modelObjDict = [modelObj dictionary];
Try using
NSDictionary *dict = [details valuesForAttributes:#[#"name", #"color"]];
And compare what the dictionary contains. Then try to convert it to JSON. And look at the JSON spec - what data types can go into a JSON encoded file?
You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping
It's a quit easy. Just describe the mapping rules in your class:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
#interface MyCustomClass : NSObject
#property (nonatomic, strong) NSNumber * someNumber;
#property (nonatomic, strong) NSString * someString;
#end
#implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = #{ #"someNumber": #"some_number",
#"someString": #"some_string" };
}
return mapping
}
#end
And then you can easily map your object to dictionary:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = #1;
myObj.someString = #"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Also you can parse your object from dictionary:
NSDictionary * myDict = #{ #"some_number": #123,
#"some_string": #"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];
Swift
Now the swift is very popular and most of the SDK's are written in Objective C, we need to convert NSObject to NSDictionary, With the Help of #thatzprem Answer, I wrote an extension for Swift which will convert our NSObject into NSDictionary, then we can use that NSDictionary to simple Dictionary or JSON Object or other purpose. I hope so this will help out the Swift User.
extension NSObject {
func convertNSObjectToNSDictionary() -> [AnyHashable : Any]? {
var dict: [AnyHashable : Any] = [:]
var count: UInt32 = 0
let properties = class_copyPropertyList(type(of: self), UnsafeMutablePointer<UInt32>(mutating: &count)) //as? objc_property_t
for i in 0..<Int(count) {
var key: String? = nil
if let property = properties?[i] as? objc_property_t {
key = String(utf8String: property_getName(property))
}
//dict[key] = (obj as? NSObject)?.value(forKey: key ?? "")
dict[key] = (self).value(forKey: key ?? "")
}
free(properties)
return dict
}
}

Objective C assigning a dictionary to a variable and accessing it

I'm sorry to ask this question again, but I'm still stuck.
I have a city object trying to fetch weather from a weather fetcher object
#interface WeatherFetcher : NSObject {
}
#property (nonatomic, strong) NSMutableDictionary *weatherData;
- (void)fetchWeather:(NSString *)cityName;
- (void)handleNetworkErorr:(NSError *)error;
- (void)handleNetworkResponse:(NSData *)myData;
#end
This is were I assign the value to weatherData
#import "WeatherFetcher.h"
#implementation WeatherFetcher
- (void)fetchWeather:(NSString *)cityName
{
NSString *urlString = #"http://api.openweathermap.org/data/2.5/weather?q=";
urlString = [urlString stringByAppendingString:cityName];
urlString = [urlString stringByAppendingString:#",Aus"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError)
{
[self handleNetworkErorr:connectionError];
}
else
{
[self handleNetworkResponse:data];
}
}];
}
#pragma mark - Private Failure Methods
- (void)handleNetworkErorr:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Network Error" message:#"Please try again later" delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
#pragma mark - Private Success Methods
- (void)handleNetworkResponse:(NSData *)myData
{
//NSMutableDictionary *data = [NSMutableDictionary dictionary];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
// now we'll parse our data using NSJSONSerialization
id myJSON = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:nil];
// typecast an array and list its contents
NSDictionary *jsonArray = (NSDictionary *)myJSON;
//NSLog([jsonArray description]);
// take a look at all elements in the array
for (id element in jsonArray) {
id key = [element description];
id innerArr = [jsonArray objectForKey:key];
NSDictionary *inner = (NSDictionary *)innerArr;
if ([inner conformsToProtocol:#protocol(NSFastEnumeration)]) {
for(id ele in inner) {
if ([ele conformsToProtocol:#protocol(NSFastEnumeration)]) {
NSDictionary *innerInner = (NSDictionary *)ele;
for(id eleEle in innerInner) {
id innerInnerKey = [eleEle description];
[data setObject:[[inner valueForKey:innerInnerKey] description] forKey:[eleEle description]];
}
}
else {
id innerKey = [ele description];
[data setObject:[[inner valueForKey:innerKey] description] forKey:[ele description]];
}
}
}
else {
[data setObject:[inner description] forKey:[element description]];
}
}
self.weatherData = data;
NSLog([self.weatherData description]) **//there is data**
}
#end
However every time I call this from by city object I get nothing back at all.
#import <Foundation/Foundation.h>
#import "WeatherFetcher.h"
#interface City : NSObject {
}
#property (nonatomic, strong) NSString *cityName;
#property (nonatomic, strong) NSString *stateName;
#property (nonatomic, strong) UIImage *cityPicture;
#property (nonatomic, strong) NSString *weather;
#property (nonatomic, strong) NSMutableDictionary *weatherData;
-(NSString *)getWeather;
#end
UI calls getWeather by a button press to get the string value to be displayed on screen
#implementation City {
}
-(NSString *)getWeather {
//return self.weather;
NSString *info = #"";
WeatherFetcher *weatherFetcher = [[WeatherFetcher alloc] init];
[weatherFetcher fetchWeather:self.cityName];
self.weatherData = [weatherFetcher weatherData];
for (id element in self.weatherData) {
info = [info stringByAppendingString:[element description]];
info = [info stringByAppendingString:#"-->"];
info = [info stringByAppendingString:[self.weatherData valueForKey:[element description]]];
info = [info stringByAppendingString:#"\n"];
}
return info;
}
#end
What am I doing wrong here?
getWeather method in the city class gets called when a button is pressed and I'm trying to display this string in a text area. I don't have much experience with Objective C and this is my first app other than Hello World app.
Thank you!
Your WeatherFetcher is asynchronous (sendAsynchronousRequest:) - it sets a task to obtain the data and then returns (usually) before that data has been obtained. So when you try to access the weatherData immediately after the call to fetchWeather: it is not there yet.
You need to redesign your model to handle asynchronicity - getWeather cannot be synchronous. For example you could make fetchWeather: take a completion block to invoke when the data is available and have getWeather pass in a suitable block.

Core Data & NSTableView Bindings

I am trying to bind my core data to a NSTableView. I am getting information from an API then wanting to add it to NSTableView. It looks like it is setup correctly because each time I have it call the API and get information back, a blank line is added to the NSTableView data.
Why is it adding a blank line instead of the data I have it binded too?
AppController.h
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
I then am using the new Xcode where it auto synth's.
Items.h
#class TimeLog;
#interface Items : NSManagedObject
#property (nonatomic, retain) NSNumber * itemId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * itemType;
#property (nonatomic, retain) TimeLog *relationship;
#end
Items.m
#implementation Items
#dynamic itemId;
#dynamic title;
#dynamic itemType;
#dynamic relationship;
#end
ItemObject.h
#interface ItemObject : NSObject
#property (nonatomic, retain) NSString * itemId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * itemType;
#end
ItemObject.m
#implementation ItemObject
#end
Method making API Call
This method makes the API call and adds it to a temp object. It then adds that temp object to core data.
+ (void)searchForItemByType:(NSString *)itemType andId:(NSString *)searchId
{
NSLog(#"Search Feature By ID: %#", searchId);
RKObjectMapping *itemMapping = [RKObjectMapping mappingForClass:[ItemObject class]];
[itemMapping addAttributeMappingsFromDictionary:#{
#"id": #"itemId",
#"name": #"title",
#"item_type": #"itemType"
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:itemMapping pathPattern:nil keyPath:#"data" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
// The entire value at the source key path containing the errors maps to the message
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"errorMessage"]];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError);
// Any response in the 4xx status code range with an "errors" key path uses this mapping
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:#"error_description" statusCodes:statusCodes];
RKObjectManager *manager = [RKObjectManager sharedManager];
NSLog(#"HTTP Client: %#", manager.HTTPClient);
[manager addResponseDescriptorsFromArray:#[ responseDescriptor, errorDescriptor ]];
// NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"false", #"with_lock"
, nil];
NSString *path = [NSString stringWithFormat:#"/api/v1/%#/%#", [itemType lowercaseString], searchId];
NSLog(#"Manager: %#", manager);
[manager getObjectsAtPath:path parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Results: %#", [result firstObject]);
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
insertItem = [result firstObject];
NSLog(#"Name: %#", [insertItem title]);
// Handled with articleDescriptor
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Transport error or server error handled by errorDescriptor
NSLog(#"Error: %#", [error localizedDescription]);
NSAlert *alert = [NSAlert alertWithMessageText:#"Error" defaultButton:#"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:#"%#", [error localizedDescription]];
[alert runModal];
}];
}
Log from above code
2013-03-15 10:15:21.817 Project[59074:403] Results: <ItemObject: 0x1034ab360>
2013-03-15 10:15:21.818 Project[59074:403] ManagedObjectContext
2013-03-15 10:15:21.818 Project[59074:403] Name: Custom Mod is missing from Face Lift
IB
I think the problem may be in this code:
NSLog(#"Results: %#", [result firstObject]);
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
insertItem = [result firstObject];
NSLog(#"Name: %#", [insertItem title]);
In your log it looks like [result firstObject] is part of the 'ItemObject' class not the 'Items' class. Even though they share the same structure, 'ItemObject' does not inherit from NSManagedObject, but is being assigned to one. The system doesn't know how to translate an 'ItemObject' object into an 'Items' object so it simply keeps all the values in insertItem blank, which translates into a blank line showing up in your table. Try this instead:
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
ItemObject *tempObject = [result firstObject];
insertItem.itemID = tempObject.itemID;
insertItem.title = tempObject.title;
insertItem.itemType = tempObject.itemType;

CoreData, transient attribute and EXC_BAD_ACCESS.

I'm trying to build simple file browser and i'm stuck.
I defined classes, build window, add controllers, views.. Everything works but only ONE time.
Selecting again Folder in NSTableView or trying to get data from Folder.files causing silent EXC_BAD_ACCESS (code=13, address0x0) from main.
Info about files i keep outside of CoreData, in simple class, I don't want to save them:
#import <Foundation/Foundation.h>
#interface TPDrawersFileInfo : NSObject
#property (nonatomic, retain) NSString * filename;
#property (nonatomic, retain) NSString * extension;
#property (nonatomic, retain) NSDate * creation;
#property (nonatomic, retain) NSDate * modified;
#property (nonatomic, retain) NSNumber * isFile;
#property (nonatomic, retain) NSNumber * size;
#property (nonatomic, retain) NSNumber * label;
+(TPDrawersFileInfo *) initWithURL: (NSURL *) url;
#end
#implementation TPDrawersFileInfo
+(TPDrawersFileInfo *) initWithURL: (NSURL *) url {
TPDrawersFileInfo * new = [[TPDrawersFileInfo alloc] init];
if (new!=nil) {
NSFileManager * fileManager = [NSFileManager defaultManager];
NSError * error;
NSDictionary * infoDict = [fileManager attributesOfItemAtPath: [url path] error:&error];
id labelValue = nil;
[url getResourceValue:&labelValue forKey:NSURLLabelNumberKey error:&error];
new.label = labelValue;
new.size = [infoDict objectForKey: #"NSFileSize"];
new.modified = [infoDict objectForKey: #"NSFileModificationDate"];
new.creation = [infoDict objectForKey: #"NSFileCreationDate"];
new.isFile = [NSNumber numberWithBool:[[infoDict objectForKey:#"NSFileType"] isEqualToString:#"NSFileTypeRegular"]];
new.extension = [url pathExtension];
new.filename = [[url lastPathComponent] stringByDeletingPathExtension];
}
return new;
}
Next I have class Folder, which is NSManagesObject subclass
// Managed Object class to keep info about folder content
#interface Folder : NSManagedObject {
NSArray * _files;
}
#property (nonatomic, retain) NSArray * files; // Array with TPDrawersFileInfo objects
#property (nonatomic, retain) NSString * url; // url of folder
-(void) reload; //if url changed, load file info again.
#end
#implementation Folder
#synthesize files = _files;
#dynamic url;
-(void)awakeFromInsert {
[self addObserver:self forKeyPath:#"url" options:NSKeyValueObservingOptionNew context:#"url"];
}
-(void)awakeFromFetch {
[self addObserver:self forKeyPath:#"url" options:NSKeyValueObservingOptionNew context:#"url"];
}
-(void)prepareForDeletion {
[self removeObserver:self forKeyPath:#"url"];
}
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == #"url") {
[self reload];
}
}
-(void) reload {
NSMutableArray * result = [NSMutableArray array];
NSError * error = nil;
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString * percented = [self.url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSArray * listDir = [fileManager contentsOfDirectoryAtURL: [NSURL URLWithString: percented]
includingPropertiesForKeys: [NSArray arrayWithObject: NSURLCreationDateKey ]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:&error];
if (error!=nil) {NSLog(#"Error <%#> reading <%#> content", error, self.url);}
for (id fileURL in listDir) {
TPDrawersFileInfo * fi = [TPDrawersFileInfo initWithURL:fileURL];
[result addObject: fi];
}
_files = [NSArray arrayWithArray:result];
}
#end
In app delegate i defined
#interface TPAppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSArrayController * foldersController;
Folder * currentFolder;
}
- (IBAction)chooseDirectory:(id)sender; // choose folder
and
- (Folder * ) getFolderObjectForPath: path {
//gives Folder object if already exist or nil if not
.....
}
- (IBAction)chooseDirectory:(id)sender {
//Opens panel, asking for url
NSOpenPanel * panel = [NSOpenPanel openPanel];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:NO];
[panel setMessage:#"Choose folder to show:"];
NSURL * currentDirectory;
if ([panel runModal] == NSOKButton)
{
currentDirectory = [[panel URLs] objectAtIndex:0];
}
Folder * folderObject = [self getFolderObjectForPath:[currentDirectory path]];
if (folderObject) {
//if exist:
currentFolder = folderObject;
} else {
// create new one
Folder * newFolder = [NSEntityDescription
insertNewObjectForEntityForName:#"Folder"
inManagedObjectContext:self.managedObjectContext];
[newFolder setValue:[currentDirectory path] forKey:#"url"];
[foldersController addObject:newFolder];
currentFolder = newFolder;
}
[foldersController setSelectedObjects:[NSArray arrayWithObject:currentFolder]];
}
Please help ;)
Ha!
_files = [NSArray arrayWithArray:result];
Should be:
_files = [[NSArray arrayWithArray:result] retain];