This is driving me crazy, think it's a simple one but any help would be great, attempting to download a remote PLIST and use it to drive config inside the app....
Getting
IMProductsDataSource.m:57:11: error: expected identifier or '('
NSURL = *remoteURL = [NSURL URLWithString:#"IOS/"];
^
IMProductsDataSource.m:58:94: error: use of undeclared identifier 'remoteURL'
NSMutableDictionary *remoteDictionary = [NSMutableDictionary dictionaryWithContentsOfURL:remoteURL];
^
2 errors generated.
Any help would be amazing...
.H
#import <Foundation/Foundation.h>
#class ProductItem;
#interface IMProductsDataSource : NSObject
#property (nonatomic, strong) NSMutableArray* productsList;
#property (nonatomic, strong) ProductItem *selectedProduct;
+ (IMProductsDataSource *)sharedInstance;
#end
.M
#import "IMProductsDataSource.h"
#import "ProductItem.h"
#import "AppConstants.h"
#interface IMProductsDataSource ()
#property(nonatomic, assign) NSInteger currentRegion;
#end
#implementation IMProductsDataSource
+ (IMProductsDataSource *)sharedInstance
{
static IMProductsDataSource *instance = nil;
#synchronized(self) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[IMProductsDataSource alloc] init];
});
}
return instance;
}
-(id)init
{
if (self = [super init])
{
self.productsList = [[NSMutableArray alloc] init];
self.currentRegion = REGIONUK;
[self loadProducts];
}
return self;
}
-(void)loadProducts {
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *dir = [path objectAtIndex:0];
NSString *filePath = [dir stringByAppendingPathComponent:#"Region1Products.plist"];
NSMutableDictionary *localDictionary;
NSURL = *remoteURL = [NSURL URLWithString:#"http://URL/IOS/"];
NSMutableDictionary *remoteDictionary = [NSMutableDictionary dictionaryWithContentsOfURL:remoteURL];
if(remoteDictionary != nil) {
[remoteDictionary writeToFile:filePath atomically:YES];
localDictionary = remoteDictionary;
}
else {
localDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
if(localDictionary == nil) localDictionary = [NSMutableDictionary dictionary];
}
// NSString *plistName = [NSString stringWithFormat:#"Region%dProducts", self.currentRegion];
// NSString *dataSourceFile = [[NSBundle mainBundle] pathForResource:
// ofType:#"plist"];
// NSArray* productsItems = [NSArray arrayWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
NSArray* productsItems = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
for (NSDictionary* productDictionary in productsItems) {
ProductItem* productItem = [[ProductItem alloc] init];
productItem.picturesCount = [productDictionary objectForKey:#"PicturesCount"];
productItem.maxPicturesCount = [productDictionary objectForKey:#"MaxPicturesCount"];
productItem.size = [productDictionary objectForKey:#"Size"];
productItem.previewImageName = [productDictionary objectForKey:#"ImageName"];
productItem.sequence = [productDictionary objectForKey:#"Sequence"];
productItem.productName = [productDictionary objectForKey:#"Name"];
productItem.type = [productDictionary objectForKey:#"ProductType"];
productItem.prices = [productDictionary objectForKey:#"Prices"];
productItem.shippingPrices = [productDictionary objectForKey:#"ShippingPrices"];
productItem.description = [productDictionary objectForKey:#"Description"];
productItem.popupMessage = [productDictionary objectForKey:#"PopupMessage"];
productItem.popupDetailMessage = [productDictionary objectForKey:#"PopupDetailMessage"];
productItem.incrementalPricing = [[productDictionary objectForKey:#"IncrementalPricing"] boolValue];
if (YES == productItem.incrementalPricing) {
productItem.incrementalPrices = [productDictionary objectForKey:#"IncrementalPrices"];
}
NSArray *previewItems = [productDictionary objectForKey:#"PreviewItems"];
for (NSDictionary* previewItem in previewItems) {
[productItem addProductPreviewItemFromDictionary:previewItem];
}
[self.productsList addObject:productItem];
}
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"sequence" ascending:YES];
self.productsList = [NSMutableArray arrayWithArray:[self.productsList sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]]];
}
#end
Your variable declaration isn't correct:
NSURL = *remoteURL = [NSURL URLWithString:#"http://URL/IOS/"];
Should be:
NSURL *remoteURL = [NSURL URLWithString:#"http://URL/IOS/"];
Note you have an extra equals sign after NSURL which is causing a syntax error.
That's a simple syntax error. This line from your code is not valid Objective-C:
NSURL = *remoteURL = [NSURL URLWithString:#"http://URL/IOS/"];
It looks like you just didn't want the first =.
Related
I have an NSURL:
serverCall?x=a&y=b&z=c
What is the quickest and most efficient way to get the value of y?
Thanks
UPDATE:
Since 2010 when this was written, it seems Apple has released a set of tools for that purpose. Please see the answers below for those.
Old-School Solution:
Well I know you said "the quickest way" but after I started doing a test with NSScanner I just couldn't stop. And while it is not the shortest way, it is sure handy if you are planning to use that feature a lot. I created a URLParser class that gets these vars using an NSScanner. The use is a simple as:
URLParser *parser = [[[URLParser alloc] initWithURLString:#"http://blahblahblah.com/serverCall?x=a&y=b&z=c&flash=yes"] autorelease];
NSString *y = [parser valueForVariable:#"y"];
NSLog(#"%#", y); //b
NSString *a = [parser valueForVariable:#"a"];
NSLog(#"%#", a); //(null)
NSString *flash = [parser valueForVariable:#"flash"];
NSLog(#"%#", flash); //yes
And the class that does this is the following (*source files at the bottom of the post):
URLParser.h
#interface URLParser : NSObject {
NSArray *variables;
}
#property (nonatomic, retain) NSArray *variables;
- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
URLParser.m
#implementation URLParser
#synthesize variables;
- (id) initWithURLString:(NSString *)url{
self = [super init];
if (self != nil) {
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
NSMutableArray *vars = [NSMutableArray new];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
[vars addObject:[tempString copy]];
}
self.variables = vars;
[vars release];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
for (NSString *var in self.variables) {
if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:#"="]]) {
NSString *varValue = [var substringFromIndex:[varName length]+1];
return varValue;
}
}
return nil;
}
- (void) dealloc{
self.variables = nil;
[super dealloc];
}
#end
*if you don't like copying and pasting you can just download the source files - I made a quick blog post about this here.
So many custom url parsers here, remember NSURLComponents is your friend!
Here is an example where I pull out a url encoded parameter for "page"
Swift
let myURL = "www.something.com?page=2"
var pageNumber : Int?
if let queryItems = NSURLComponents(string: myURL)?.queryItems {
for item in queryItems {
if item.name == "page" {
if let itemValue = item.value {
pageNumber = Int(itemValue)
}
}
}
}
print("Found page number: \(pageNumber)")
Objective-C
NSString *myURL = #"www.something.com?page=2";
NSURLComponents *components = [NSURLComponents componentsWithString:myURL];
NSNumber *page = nil;
for(NSURLQueryItem *item in components.queryItems)
{
if([item.name isEqualToString:#"page"])
page = [NSNumber numberWithInteger:item.value.integerValue];
}
"Why reinvent the wheel!" - Someone Smart
I'm pretty sure you have to parse it yourself. However, it's not too bad:
NSString * q = [myURL query];
NSArray * pairs = [q componentsSeparatedByString:#"&"];
NSMutableDictionary * kvPairs = [NSMutableDictionary dictionary];
for (NSString * pair in pairs) {
NSArray * bits = [pair componentsSeparatedByString:#"="];
NSString * key = [[bits objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString * value = [[bits objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[kvPairs setObject:value forKey:key];
}
NSLog(#"y = %#", [kvPairs objectForKey:#"y"]);
In Swift you can use NSURLComponents to parse the query string of an NSURL into an [AnyObject].
You can then create a dictionary from it (or access the items directly) to get at the key/value pairs. As an example this is what I am using to parse a NSURL variable url:
let urlComponents = NSURLComponents(URL: url, resolvingAgainstBaseURL: false)
let items = urlComponents?.queryItems as [NSURLQueryItem]
var dict = NSMutableDictionary()
for item in items{
dict.setValue(item.value, forKey: item.name)
}
println(dict["x"])
I've been using this Category: https://github.com/carlj/NSURL-Parameters.
It's small and easy to use:
#import "NSURL+Parameters.h"
...
NSURL *url = [NSURL URLWithString:#"http://foo.bar.com?paramA=valueA¶mB=valueB"];
NSString *paramA = url[#"paramA"];
NSString *paramB = url[#"paramB"];
You can use Google Toolbox for Mac.
It adds a function to NSString to convert query string to a dictionary.
http://code.google.com/p/google-toolbox-for-mac/
It works like a charm
NSDictionary * d = [NSDictionary gtm_dictionaryWithHttpArgumentsString:[[request URL] query]];
Here's a Swift 2.0 extension that provides simple access to parameters:
extension NSURL {
var params: [String: String] {
get {
let urlComponents = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)
var items = [String: String]()
for item in urlComponents?.queryItems ?? [] {
items[item.name] = item.value ?? ""
}
return items
}
}
}
Sample usage:
let url = NSURL(string: "http://google.com?test=dolphins")
if let testParam = url.params["test"] {
print("testParam: \(testParam)")
}
I wrote a simple category to extend NSString/NSURL that lets you extract URL query parameters individually or as a dictionary of key/value pairs:
https://github.com/nicklockwood/RequestUtils
I did it using a category method based on #Dimitris solution
#import "NSURL+DictionaryValue.h"
#implementation NSURL (DictionaryValue)
-(NSDictionary *)dictionaryValue
{
NSString *string = [[self.absoluteString stringByReplacingOccurrencesOfString:#"+" withString:#" "]
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *temp;
NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&temp])
{
NSArray *parts = [temp componentsSeparatedByString:#"="];
if([parts count] == 2)
{
[dict setObject:[parts objectAtIndex:1] forKey:[parts objectAtIndex:0]];
}
}
return dict;
}
#end
All of the current answers are version specific or needlessly wasteful. Why create a dictionary if you only want one value?
Here's a simple answer that supports all iOS versions:
- (NSString *)getQueryParam:(NSString *)name fromURL:(NSURL *)url
{
if (url)
{
NSArray *urlComponents = [url.query componentsSeparatedByString:#"&"];
for (NSString *keyValuePair in urlComponents)
{
NSArray *pairComponents = [keyValuePair componentsSeparatedByString:#"="];
NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
if ([key isEqualToString:name])
{
return [[pairComponents lastObject] stringByRemovingPercentEncoding];
}
}
}
return nil;
}
You can do that easy :
- (NSMutableDictionary *) getUrlParameters:(NSURL *) url
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *tmpKey = [url query];
for (NSString *param in [[url query] componentsSeparatedByString:#"="])
{
if ([tmpKey rangeOfString:param].location == NSNotFound)
{
[params setValue:param forKey:tmpKey];
tmpKey = nil;
}
tmpKey = param;
}
[tmpKey release];
return params;
}
It return Dictionary like it : Key = value
I edited Dimitris' code slightly for better memory management and efficiency. Also, it works in ARC.
URLParser.h
#interface URLParser : NSObject
- (void)setURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
URLParser.m
#import "URLParser.h"
#implementation URLParser {
NSMutableDictionary *_variablesDict;
}
- (void)setURLString:(NSString *)url {
[_variablesDict removeAllObjects];
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
NSString *dataString = [tempString copy];
NSArray *sepStrings = [dataString componentsSeparatedByString:#"="];
if ([sepStrings count] == 2) {
[_variablesDict setValue:sepStrings[1] forKeyPath:sepStrings[0]];
}
}
}
- (id)init
{
self = [super init];
if (self) {
_variablesDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
NSString *val = [_variablesDict valueForKeyPath:varName];
return val;
return nil;
}
-(NSString *)description {
return [NSString stringWithFormat:#"Current Variables: %#", _variablesDict];
}
#end
Quickest is:
NSString* x = [url valueForQueryParameterKey:#"x"];
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];
}
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
}
}
hi i tried to add values(book id,page number,notes) from NSdictionary to Plist but each time the new value replacing the previous one?but i need all values in plist my code for adding dictionary to plist is
NSString *bid=#"95";
NSString *pnum=#"12";
userNotes=[[NSMutableDictionary alloc]init];
[userNotes setValue:userNotesTextview.text forKey:#"notes"];
[userNotes setValue:bid forKey:#"bookid"];
[userNotes setValue:pnum forKey:#"pagenumber"];
userNotesView.hidden=YES;
_background.hidden = YES;
userNotesTextview.text=#"";
[self savingMetaData];
NSMutableArray *notes=[[NSMutableArray alloc]init];
[notes addObject:userNotes];
NSMutableDictionary *final=[[NSMutableDictionary alloc]init];
[final setValue:notes forKey:#"usernotes"];
[final writeToFile:metaDataPath atomically:YES];
and my plist look like
how can i solve this problem
Fetch the existing array from the plist as below, but first make sure you have copied you plist to Documents directory or, to some writable folder as below
NSFileManager *fileManager=[NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *docPath=[[paths objectAtIndex:0] stringByAppendingString:#"yourplist.plist"];
BOOL fileExists = [fileManager fileExistsAtPath: docPath];
NSError *error = nil;
if(!fileExists)
{
NSString *strSourcePath = [[NSBundle mainBundle] pathForResource:#"yourplist" ofType:#"plist"];
[fileManager copyItemAtPath:strSourcePath toPath:docPath error:&error];
}
NSString *path = docPath;
NSMutableDictionary *plistdictionary = [[NSMutableDictionary alloc]initWithContentsOfFile:path];
NSMutableArray *notes=[plistdictionary objectForKey:#"usernotes"];
if(notes==nil){
notes=[[NSMutableArray alloc] init];
}
NSString *bid=#"95";
NSString *pnum=#"12";
userNotes=[[NSMutableDictionary alloc]init];
[userNotes setValue:userNotesTextview.text forKey:#"notes"];
[userNotes setValue:bid forKey:#"bookid"];
[userNotes setValue:pnum forKey:#"pagenumber"];
[notes addObject:userNotes];
then finally
NSMutableDictionary *final=[[NSMutableDictionary alloc]init];
[final setValue:notes forKey:#"usernotes"];
[final writeToFile:docPath atomically:YES];
Note: You cannot write anything in MainBundle, so better to copy your plist to Documents directory and use from there..
because plist can store value with unique key only. if you try to save value with same key it will replace old one with new value. so always save new value with new key (eg. item0, item1, item3 etc.)
following line will store two usernote with key #"usernotes1" and #"usernotes2" respectively
[final setValue:notes forKey:#"usernotes1"];
[final setValue:notes forKey:#"usernotes2"];
Plist structure looks like this
You can create a UserNote model class.
#define kBookID #"bookid"
#define kPageNumber #"pageNumber"
#define kNotes #"notes"
#interface UserNote : NSObject
#property (nonatomic, copy) NSString *bookID;
#property (nonatomic, copy) NSString *pageNumber;
#property (nonatomic, copy) NSString *notes;
- (id)initWithDictionary:(NSDictionary *)dictionary;
+ (NSArray *)savedUserNotes;
- (void)save;
#end
Initialize
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self)
{
self.bookID = dictionary[kBookID];
self.pageNumber = dictionary[kPageNumber];
self.notes = dictionary[kNotes];
}
return self;
}
Find the document path of plist file in documents directory. If the plist file is not there create a new one and return the path.
+ (NSString *)userNotesDocumentPath
{
NSString *documentsPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"UserNotes.plist"];
NSFileManager *fileManger = [NSFileManager defaultManager];
if (![fileManger fileExistsAtPath:documentsPath])
{
NSString *bundleResourcePath = [[NSBundle mainBundle]pathForResource:#"UserNotes" ofType:#"plist"];
NSArray *userNotes = [NSArray arrayWithContentsOfFile:bundleResourcePath];
[userNotes writeToFile:documentsPath atomically:YES];
}
return documentsPath;
}
Fetches all saved usernotes from plist file.
+ (NSArray *)savedUserNotes
{
NSString *documentsPath = [self userNotesDocumentPath];
NSArray *savedNotes = [NSArray arrayWithContentsOfFile:documentsPath];
NSMutableArray *savedUserNotes = [#[] mutableCopy];
for (NSDictionary *dict in savedNotes) {
UserNote *note = [[UserNote alloc]initWithDictionary:dict];
[savedUserNotes addObject:note];
}
return savedUserNotes;
}
Saves a usenote to plist
- (NSDictionary *)userNoteDictionary
{
return #{kBookID:self.bookID,kPageNumber:self.pageNumber,kNotes:self.notes};
}
- (void)saveUserNotesToPlist:(NSArray *)userNotes
{
NSMutableArray *mutableUserNotes = [#[] mutableCopy];
for (UserNote *note in userNotes) {
NSDictionary *dict = [note userNoteDictionary];
[mutableUserNotes addObject:dict];
}
NSString *documentsPath = [UserNote userNotesDocumentPath];
[mutableUserNotes writeToFile:documentsPath atomically:YES];
}
#pragma mark - Save
- (void)save
{
NSMutableArray *savedNotes = [[UserNote savedUserNotes] mutableCopy];
[savedNotes addObject:self];
[self saveUserNotesToPlist:savedNotes];
}
In you viewController where user makes a note
- (IBAction)saveUserNoteButtonPressed:(UIButton *)button
{
UserNote *note = [UserNote new];
note.bookID = #"95";
note.pageNumber = #"12";
note.notes = self.userNotesTextview.text;
[note save];
}
Demo Source Code
my NSMutableArray creation code is being bypassed altogether for some reason. in theory it is supposed to create an NSMutableArray based on an sqlite database. There is only one warning message and no errors. what am I missing?
the implementation file is:
#import "iProspectFresno LiteAppDelegate.h"
#import "MainViewController.h"
#import "Mine.h"
#import <Foundation/Foundation.h>
#implementation iProspectFresno_LiteAppDelegate
#synthesize window;
#synthesize mainViewController;
#synthesize mines;
-(void) checkAndCreateDatabase {
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
success = [fileManager fileExistsAtPath:databasePath];
if(success) return;
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
[fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];
}
-(void) readMinesFromDatabase
{
sqlite3 *database;
mines = [[NSMutableArray alloc] init];
NSLog(#"readMinesFromDatabase initialized");
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = "select * from MinesoftheMotherLode";
sqlite3_stmt *compiledStatement;
NSLog(#"first if statement");
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
{
NSLog(#" second if statement initialized");
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
NSNumber *aentryNumber = [NSNumber numberWithInt:(int)sqlite3_column_int(compiledStatement, 1)];
NSString *amineName = [NSString stringWithUTF8String:(char*)sqlite3_column_text(compiledStatement, 2)];
NSString *amineType = [NSString stringWithUTF8String:(char*)sqlite3_column_text(compiledStatement, 3)];
NSString *astatus = [NSString stringWithUTF8String:(char*)sqlite3_column_text(compiledStatement, 4)];
NSNumber *alatitude = [NSNumber numberWithDouble:(double)sqlite3_column_double(compiledStatement, 5)];
NSNumber *alongitude = [NSNumber numberWithDouble:(double)sqlite3_column_double(compiledStatement, 6)];
NSString *ametal =[NSString stringWithUTF8String:(char*)sqlite3_column_text(compiledStatement, 7)];
BOOL *adisplay = NO;
NSNumber *acoverRegion =[NSNumber numberWithInt:(int)sqlite3_column_int(compiledStatement, 9)];
NSLog(#"mine", aentryNumber, amineName, amineType, astatus, alatitude, alongitude, ametal, adisplay, acoverRegion);
Mine *mine = [[Mine alloc] initWithEntryNumber:aentryNumber mineName:amineName mineType:amineType status:astatus latitudeInitial:alatitude longitudeInitial:alongitude metal:ametal display:adisplay coverRegion:acoverRegion];
[mines addobject:mine];
[mine release];
}
}
NSLog(#"created database successfully");
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
databaseName = #"MinesoftheMotherLode.sql";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
[self checkAndCreateDatabase];
[self readMinesFromDatabase];
MainViewController *aController = [[MainViewController alloc] initWithNibName:#"MainView" bundle:nil];
self.mainViewController = aController;
[aController release];
mainViewController.view.frame = [UIScreen mainScreen].applicationFrame;
[window addSubview:[mainViewController view]];
[window makeKeyAndVisible];
}
The implementation file for Mines is here:
#import "Mine.h"
#implementation Mine
#synthesize entryNumber, mineName, mineType, status, latitudeInitial, longitudeInitial, metal, display, coverRegion;
-(id)initWithEntryNumber:(NSNumber *)e mineName:(NSString *)n mineType:(NSString *)t status:(NSString *)s latitudeInitial:(NSNumber *)l longitudeInitial:(NSNumber *)o metal:(NSString *)m display:(BOOL *)d coverRegion:(NSNumber *)c
{
self.entryNumber = e;
self.mineName = n;
self.mineType = t;
self.status = s;
self.latitudeInitial = l;
self.longitudeInitial = o;
self.metal = m;
self.display = d;
self.coverRegion = c;
return self;
}
#end
The NSLog "Second if statement initialized" is not showing up on the console. any ideas as to what needs to be fixed here? and yes I know, I should be using core data.
It seems you've answered your initial question about it loading. For your secondary question regarding the crash about the null string, you should be loading strings like this:
if (sqlite3_column_text(init_statement, 0) != NULL) {
self.someString = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];
} else {
self.someString = #"";
}