Read and update a Dictionary type PSMultiValueSpecifier from NSUserDefault - objective-c

Does anyone know how to easily read a Dictionary type PSMultiValueSpecifier from NSUserDefault into a UIPickViewer control and save changes back to NSUserDefaults using iPhone SDK 3.0? If so, can you post some specific code to efficiently do this?

The following code turns the PSMultiValueSpecifier Values and Titles into a nice dictionary which you can use with your UIPickViewer.
NSString* settingsBundle = [[[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"] stringByAppendingPathComponent:#"Root.plist"];
NSDictionary* rootPlist = [NSDictionary dictionaryWithContentsOfFile:settingsBundle];
if (rootPlist == nil)
return nil;
NSArray* specifiers = [rootPlist objectForKey:#"PreferenceSpecifiers"];
NSDictionary *multiValueSpecifier = nil;
for (NSDictionary *specifier in specifiers)
{
if ([[specifier objectForKey:#"Key"] isEqualToString:speficierKey] == YES &&
[[specifier objectForKey:#"Type"] isEqualToString:#"PSMultiValueSpecifier"] == YES)
{
multiValueSpecifier = specifier;
break;
}
}
if (multiValueSpecifier == nil)
return nil;
NSArray* titlesArray = [multiValueSpecifier objectForKey:#"Titles"];
NSArray* valuesArray = [multiValueSpecifier objectForKey:#"Values"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:titlesArray
forKeys:valuesArray];
return dictionary;
To save it you need to save the KEY and not the value of the dictionary.
[settings setObject:dictionaryKey forKey:#"mySelection_preference"];

Related

iOS Crash: " this class is not key value coding-compliant for the key url"

I am getting the following crash on crashlytics.
Fatal Exception: NSUnknownKeyException
[<__NSCFString 0x1742aeb80> valueForUndefinedKey:]: this class is not key value coding-compliant for the key url.
This is where the crash occurred for some of the users.
id url = [[json[#"data"]valueForKey:#"value"]valueForKey:#"url"];
I'm not sure what is the best way to prevent this crash. I believe this is because json[#"data"] is an NSString in certain cases. So I believe I should check if this is an NSDictionary like this.
if ([json[#"data"] isKindOfClass:[NSDictionary class]]) {
id url = [[json[#"data"]valueForKey:#"value"]valueForKey:#"url"];
}
Any tips or suggestions are appreciated.
This is my end result after getting answers from here. Does this look okay? I didn't include all my code at first to keep things simple.
if ([json isKindOfClass:[NSDictionary class]]) {
id url = nil;
id type = nil;
NSDictionary *data = json[#"data"];
if ([data isKindOfClass:[NSDictionary class]]) {
type = data[#"type"];
NSDictionary *value = data[#"value"];
if ([value isKindOfClass:[NSArray class]]) {
url = [value valueForKey:#"url"];
}
if ([type isKindOfClass:[NSString class]] && [url isKindOfClass:[NSArray class]] && [url count] != 0) {
// do stuff
}
}
}
You should check NSDictionary one by one to prevent crash. Try my code below
NSDictionary *dictionary = json[#"data"];
NSString *output = #"";
if ([dictionary isKindOfClass:[NSDictionary class]]) {
dictionary = dictionary[#"value"];
if ([dictionary isKindOfClass:[NSDictionary class]]) {
output = dictionary[#"url"];
}
}
NSLog(#"%#", output);
You got crash because of calling valueForKey method on a NSString value. If someone says the reason for crash is call valueForKey when dictionary doesn't have this key, it's wrong. For more information Sending a message to nil in Objective-C
[dictionary isKindOfClass:[NSDictionary class]] always return NO if dictionary is nil so don't need to check dictionary in if statement. It's unnessary.
Your error means that json[#"data"]valueForKey:#"value"] doesn't NSDictionarry, so it have no #"url" key.
valueForKey it's KVC method, use objectForKey for dictionaries, and add more checks like:
id url = nil;
NSDictionary *data = [json objectForkey:#"data"];
if ([data isKindOfClass:[NSDictionary class]]) {
NSDictionary *value = [data objectForKey:#"value"];
if ([value isKindOfClass:[NSDictionary class]]) {
url = [value objectForKey:#"url"];
}
}

NSDictionary writeToFile fails while objects are valid, permission is 0k

Why NSDictionary cannot be written?? I have checked the content of the dictionary: all the instances are of NSString and NSNumber. I checked permissions: a text file with the same name at the same path is written well. Of course, my dictionary is not empty.
NSString *file = ...
NSDictionary *dict = ...
// check dictionary keys
BOOL wrong = NO;
for (id num in [dict allKeys]) {
if (![num isKindOfClass:[NSNumber class]]) {
wrong = YES;
break;
}
}
if (wrong) {
NSLog(#"First");
}
// check dictionary values
wrong = NO;
for (id num in [dict allValues]) {
if (![num isKindOfClass:[NSString class]]) {
wrong = YES;
break;
}
}
if (wrong) {
NSLog(#"Second");
}
if (![dict writeToFile:file atomically:YES]) {
// 0k, let's try to create a text file
NSLog(#"Names writing error!");
[#"Something here... .. ." writeToFile:file atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
Output: "Names writing error!"
Text file is created successfully.
Writing out a dictionary creates a property list, and according to the documentation all keys in a property list must be strings.
... and although NSDictionary and CFDictionary objects allow their keys to
be objects of any type, if the keys are not string objects, the
collections are not property-list objects.
NSNumber objects as keys are not supported.
As #vadian points out, you cannot write plist with numeric keys. But you can use NSKeyedArchiver:
NSURL *documents = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:nil];
NSURL *fileURL = [documents URLByAppendingPathComponent:#"test.plist"];
// this will not work
NSDictionary *dictionary = #{#1: #"foo", #2: #"bar"};
BOOL success = [dictionary writeToFile:fileURL.path atomically:true];
NSLog(#"plist %#", success ? #"success" : #"failure");
// this will
fileURL = [documents URLByAppendingPathComponent:#"test.bplist"];
success = [NSKeyedArchiver archiveRootObject:dictionary toFile:fileURL.path];
NSLog(#"archive %#", success ? #"success" : #"failure");
And you can read it back with NSKeyedUnarchiver:
// to read it back
NSDictionary *dictionary2 = [NSKeyedUnarchiver unarchiveObjectWithFile:fileURL.path];
NSLog(#"dictionary2 = %#", dictionary2);
Note, you can do this with any class that conforms (and properly implements) NSCoding. Fortunately, NSDictionary conforms already. You have to make sure that any objects inside the dictionary, also conform (both NSString and NSNumber do). If you had a custom object in your dictionary, you'd have to make it properly conform yourself.
This is all described in the Archives and Serializations Programming Guide.

UITableView reloadData crashes with error [__NSCFConstantString objectForKey:]

I am parsing data from server and display this data in my app. This data is a JSON data and it looks like this:
{"getMessages":[{"msgid":"1","message":"Hello.","dateposted":"2012-08-28"}]}
That's when a message is available to be sent, however, if no messages were available, JSON will look like this:
{"status":"No messages available"}
In my app, I use NSJSONSerialization to parse the JSON. Here is how I do it:
if ([data length] > 0)
{
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:data options
NSJSONReadingMutableContainers error:nil];
if (![parsedData objectForKey:#"getMessages"])
{
[self.messageArray addObject:#"No Messages"];
}
else
{
self.messageArray = (NSMutableArray *)[parsedData objectForKey:#"getMessages"];
}
}
As you can see, when the parsedData has no getMessages key, it will add the No Messages in
self.messageArray, but if it has the key, it will add the values related to it.
self.messageArray was the array I used to populate the messageTable. At the end of the download, I put the code [messageTable reloadData].
The problem is this: Reloading the table works if the parsedData contains the key getMessages. However, if the key was not found, reloading the table crashes.
This is my tableView:cellForRowAtIndexPath method:
NSString *tableIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if (cell == nil)
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:tableIdentifier] autorelease];
if (tableView == messageTable) //I do this since there is another table I am using
{
NSString *string = [NSString stringWithFormat:#"%#", [self.messageArray objectAtIndex:0]];
if ([string isEqualToString:#"No Messages"])
{
cell.textLabel.text = string;
}
else
{
NSDictionary *dict = [self.messageArray objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#", [dict objectForKey:#"message"]];
}
}
In messageTable, I check first if the first index of self.messageArray is equal to the string "No Messages", this is to let the user know that no messages can be retrieved. If the
string is not equal, it will then assume that the data inside the array is a dictionary and therefore, it will be parsed to display the message.
After making use of breakpoints and logs, I realized that the crash happens while reloading the table. I inserted a breakpoint and a log at the start of the method tableView:cellForRowAtIndexPath but it never even got there. I tried checking the content of the self.messageArray and it does contain "No Messages".
The crash tells me this error: [__NSCFConstantString objectForKey:]: unrecognized selector sent to instance 0x10ed84
I know that this error is telling me that I am calling the method objectForKey in a NSString, but I really don't know why. Can anyone help me here?
try this
[self.messageArray removeAllObjects];
[self.messageArray addObject:[parsedData setObject:#"No Messages" forKey:#"getMessages"]];
mostly the dictionary is not setting for the key.. just check..
Also,
NSDictionary *diction = [self.messageArray objectAtIndex:0];
NSString *string = [NSString stringWithFormat:#"%#", [diction objectForKey:#"getMessages"]];
if ([string isEqualToString:#"No Messages"])
{
cell.textLabel.text = string;
}
else
{
NSDictionary *dict = [self.messageArray objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#", [dict objectForKey:#"message"]];
}
First make sure that messageArray is defined as NSMutableArray... and if you are reloading your parsedData from time to time then try this
if (![parsedData objectForKey:#"getMessages"])
{
[self.messageArray removeAllObjects];
[self.messageArray addObject:#"No Messages"];
}
else
{
[self.messageArray removeAllObjects];
self.messageArray = [[parsedData objectForKey:#"getMessages"] mutableCopy];
}

Can I get AFNetworking to automatically parse NULL to nil?

We're using AFNetworking in our mobile app and a lot of times we will have JSON come back that has null for some values.
I'm getting tired of doing the following.
if ([json objectForKey:#"nickname"] isKindOfClass:[NSNull class]]) {
nickname = nil;
} else {
nickname = [json objectForKey:#"nickname"];
}
Anything we can do to make AFNetworking automagically set objects to nil or numbers to 0 if the value is null in the JSON response?
You can set flag setRemovesKeysWithNullValues to YES in AFHTTPSessionManager response serializer:
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithBaseURL:url sessionConfiguration:config];
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
[serializer setRemovesKeysWithNullValues:YES];
[manager setResponseSerializer:serializer];
It's not really possible, since the dictionary can't contain nil as the object for a key. The key would have to be left out entirely in order to get the behavior you'd want, which would be undesirable in its own way.
Suppose you didn't have control over the data you were receiving and didn't know what keys were present in the JSON. If you wanted to list them all, or display them in a table, and the keys for null objects were left out of the dictionary, you'd be seeing an incorrect list.
NSNull is the "nothing" placeholder for Cocoa collections, and that's why it's used in this case.
You could make your typing a bit easier with a macro:
#define nilOrJSONObjectForKey(JSON_, KEY_) [[JSON_ objectForKey:KEY_] isKindOfClass:[NSNull class]] ? nil : [JSON_ objectForKey:KEY_]
nickname = nilOrJSONObjectForKey(json, #"nickname");
DV_'s answer works great for AFHTTPSessionManager. But if you are using AFHTTPRequestOperation instead of the manager, try this:
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
serializer.removesKeysWithNullValues = YES;
op.responseSerializer = serializer;
There is one beautiful cocoapod called Minced https://github.com/hyperoslo/Minced that can do something that can help you handle NULL from JSON response. Instead of NULL it puts empty string.
If you replace the default NSJSONSerialization with SBJSON it will solve your problem.
SBJSON makes objects nil instead of NSJSONSerialization's choice of "null"
look at the requirements for the different JSON parsers you can use.
https://github.com/AFNetworking/AFNetworking#requirements
You can custom AFNetworking at this functions. set any value default to objects that is NULL
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
if (!value || [value isEqual:[NSNull null]]) {
// custom code here
//[mutableDictionary removeObjectForKey:key];
[mutableDictionary setObject:#"" forKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return JSONObject;
}

How to archive an NSArray of custom objects to file in Objective-C

Can you show me the syntax or any sample programs to archive an NSArray of custom objects in Objective-C?
Check out NSUserDefaults.
For Archiving your array, you can use the following code:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:myArray] forKey:#"mySavedArray"];
And then for loading the custom objects in the array you can use this code:
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *savedArray = [currentDefaults objectForKey:#"mySavedArray"];
if (savedArray != nil)
{
NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:savedArray];
if (oldArray != nil) {
customObjectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
} else {
customObjectArray = [[NSMutableArray alloc] init];
}
}
Make sure you check that the data returned from the user defaults is not nil, because that may crash your app.
The other thing you will need to do is to make your custom object to comply to the NSCoder protocol. You could do this using the -(void)encodeWithCoder:(NSCoder *)coder and -(id)initWithCoder:(NSCoder *)coder methods.
If you want to save to a file (rather than using NSUserDefaults) you can use -initWithContentsOfFile: to load, and -writeToFile:atomically: to save, using NSArrays.
Example:
- (NSArray *)loadMyArray
{
NSArray *arr = [NSArray arrayWithContentsOfFile:
[NSString stringWithFormat:#"%#/myArrayFile", NSHomeDirectory()]];
return arr;
}
// returns success flag
- (BOOL)saveMyArray:(NSArray *)myArray
{
BOOL success = [myArray writeToFile:
[NSString stringWithFormat:#"%#/myArrayFile", NSHomeDirectory()]];
return success;
}
There's a lot of examples on various ways to do this here: http://www.cocoacast.com/?q=node/167