setValue:forKey: with a NSString - iphone

I'm currently working on a small and specific iPhone app to connect to a server and download some JSON data. My problem is about a property list I use to save some data (login, password and domain of the server). I've easily created the plist in Xcode but when I try to edit it with the texts entered by the user, I'm having a problem and I don't know how to fix it ...
Here is the textFieldDidEndEditing: method, in which I try to write to the plist, with what the user have written in a domainTextField :
- (void)textFieldDidEndEditing:(UITextField *)textField
{
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filepath = [NSString stringWithFormat:#"%#/userInfo.plist", [paths objectAtIndex:0]];
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithContentsOfFile:filepath];
if (self.domainTextField) {
NSString *domain = [NSString stringWithFormat:#"%#", self.domainTextField.text];
[userInfo setValue:domain forKey:#"domain"];
}
if ([userInfo writeToFile:filepath atomically:TRUE]) {
NSLog(#"write succeeded");
} else {
NSLog(#"write failed");
}
}
I'm always receiving a "write failed" error here and after playing a bit with the debugger, I found that the problem is with the setValue:forKey: method. In fact, I expect this method to write that in the plist : domain = "whateverthedomainis" because domain is a NSString but it writes instead : domain = whateverthedomainis. And as the writeToFile:atomically: method makes sure that all the objects are property list objects, it returns NO. I tried to put backslashes, but that didn't help.
What can I do to resolve this error?

StringWithFormat will use if you have any arguments in a string so you can directly assign a string value ,like this
NSString *domain = self.domainTextField.text;

Related

Write JSON Response to .plist File

Frustration on the Top !!!
I am getting some JSON Response from the Service and I want to store it in the .plist file for Future Reference.
I am unable to save my JSON Response to .plist File. I think it's due to some null values into the Response.
Note : I confirmed that the Response is in JSON Format using jsonparser.
My Code :
NSError *error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSDictionary *dictResult = [(NSDictionary*)json objectForKey:#"myKey"];
NSLog(#"Result Dictionary :: %#",dictResult);
NSURL *cacheDir = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *path = [cacheDir URLByAppendingPathComponent:#"FinalResult.plist"];
NSLog(#"Path :: %#",path);
BOOL success = [dictResult writeToURL:path atomically:YES];
NSLog(#"success? %d", success);
Note : I got all the NSLog Values (means the Response Dictionary and File Path but 0 for success).
Problem : There are almost 70-80 key-value pairs in the Response and I don't want to remove/replace all the null values. Because What I want is ...
GET the Response From the Server.
Fill all the UITextFields with the Response.
POST the Same Response to the Server with some Edited Values from the UITextFields.
So, I just want to change the Edited UITextField values in the Object and let it POST to the Server.
What is the Optimum Way to Fix this ?
I bet that your JSON contains at least one null value.
When you have JSON that contains null and you convert it using NSJSONSerialization, the null is replaced by an instance of NSNull. If your dictionary contains NSNull, then writeToURL:atomically: will fail.
This is because the convenience methods for reading and writing dictionaries (and arrays) only work if the data in the collection is restricted to property list types. These are:
NSString
NSNumber
NSData
NSDate
NSArray
NSDictionary. And for dictionaries, the keys must be NSStrings.
You can also use mutable subclasses (like NSMutableString) when writing.
If you have anything not on that list, you can't use writeToURL:atomically or any of the similar convenience methods.
The problem is that some valid JSON can't be converted to property lists. Also, some valid property lists can't be converted to JSON (because NSDate won't automatically convert to valid JSON).
If it was me, I'd just write the data to a JSON file. Leave it in its original format. You can convert to/from JSON easily, so leave it that way.
If your dictionary contains NSNull, then writeToURL:atomically: will fail.
For dictionaries, the keys must be NSStrings.
The problem is that some valid JSON can't be converted to property lists. Also, some valid property lists can't be converted to JSON.
Don't Forget, If you must use a property list, you will need to scan the entire dictionary and convert the nulls into something that can be saved in a property list file.
Only Solution is that you have to check all the NULL Values and Replace it with #" ".
Happy Coding...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryPath = [paths objectAtIndex:0];
NSString *filename = #"FinalResult.plist";
NSString *pathFilename = [libraryPath stringByAppendingPathComponent:filename];
NSDictionary *dictResult = [json objectForKey:#"myKey"];
[dictResult writeToFile:pathFilename atomically:YES];
I build my file urls this way:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSString *filename = #"FinalResult.plist";
NSString *pathFilename = [path stringByAppendingPathComponent:filename];
Then see if this writes:
BOOL success = [dictResult writeToFile:pathFilename atomically:YES];
NSLog(#"success? %d", success);
Edit - Funny, I just recently confronted this problem and then forgot all about it. Some JSON parsers will use [NSNull null] as placeholders for nil values. I wrote this (seriously, just about two weeks ago and then spaced on it) to clean up the parse result...
- (NSDictionary *)compact:(NSDictionary *)aDictionary {
NSDictionary *answer = [NSMutableDictionary dictionary];
for (NSString *key in [aDictionary allKeys]) {
id value = [self.dictionary valueForKey:key];
if (value && value != [NSNull null]) {
[answer setValue:value forKey:key];
}
}
return [NSDictionary dictionaryWithDictionary:answer];
}
This could be made into a NSDictionary category addition if you wanted.
Check if the return value of the writeToURL:atomically: method returns YES.
If it doesn't, check if you have write permissions to that URL.
Try it with another URL to find out if its the path or the contents of the dictionary that's causing this error.
Try this, it will work :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"country.plist"];
[jsonData writeToFile:path atomically:YES];

Can I write an array of UILocalNotifications to disk?

I'm trying to use the following code to persist the current list of local notifications. NSArray explicitly lists the kind of objects it will work with, which implies I can not use this with an array full of UILocalNotification objects. However, UILocalNotifications does implement NSCoding, which led me to believe there must be an easy way to serialize/deserialize this list of objects. Do I need to do the encoding and file persistence myself? Also, is there a way to get more information about why the write failed?
- (NSString*)getSavedNotifsPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
}
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
if (! [existingNotifications writeToFile:[self getSavedNotifsPath] atomically:NO] ) {
// alert
[self showSomething:#"write failed"];
}
}
First, change the code
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
to
return [documentsDirectory stringByAppendingPathComponent:#"saved_notifs.plist"];
stringByAppendingPathComponent: will ensure a slash (/) is included, if needed, before the file name.
NSArray can only save property list objects, which UILocalNotification is not. Instead, try using NSKeyedArchiver. For example:
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
NSString *path = [self getSavedNotifsPath];
BOOL success = [NSKeyedArchiver archiveRootObject:existingNotifications toFile:path];
if (! success ) {
// alert
[self showSomething:#"write failed"];
}
}
Use NSKeyedUnarchiver to retrieve the array from the saved file.
Note: I have not actually tested this so I'm not 100% sure it will work. But give it a try and see what happens.

Why doesen't it work to write this NSMutableArray to a plist?

edited.
Hey, I am trying to write an NSMutableArray to a plist.
The compiler does not show any errors, but it does not write to the plist anyway.
I have tried this on a real device too, not just the Simulator.
Basically, what this code does, is that when you click the accessoryView of a UITableViewCell, it gets the indexPath pressed, edits an NSMutableArray and tries to write that NSMutableArray to a plist. It then reloads the arrays mentioned (from multiple plists) and reloads the data in a UITableView from the arrays.
Code:
NSIndexPath *indexPath = [table indexPathForRowAtPoint:[[[event touchesForView:sender] anyObject] locationInView:table]];
[arrayFav removeObjectAtIndex:[arrayFav indexOfObject:[NSNumber numberWithInt:[[arraySub objectAtIndex:indexPath.row] intValue]]]];
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"arrayFav.plist"];
NSLog(#"%# - %#", rootPath, plistPath);
[arrayFav writeToFile:plistPath atomically:YES];
// Reloads data into the arrays
[self loadDataFromPlists];
// Reloads data in tableView from arrays
[tableFarts reloadData];
CFShow() on the array after removing one of them shows this:
<CFArray 0x6262110 [0x2c810a0]>{type = mutable-small, count = 4, values = (
0 : <CFNumber 0x6502e10 [0x2c810a0]>{value = +3, type = kCFNumberSInt32Type}
1 : <CFNumber 0x6239de0 [0x2c810a0]>{value = +8, type = kCFNumberSInt32Type}
2 : <CFNumber 0x6239dc0 [0x2c810a0]>{value = +10, type = kCFNumberSInt32Type}
3 : <CFNumber 0x6261420 [0x2c810a0]>{value = +40, type = kCFNumberSInt64Type}
DEBUG-INFO: writeToPlist shows YES, I have tried to release all the arrays before filling them up again, setting them to nil, set atomically to NO.
As discussed in the comments below, the actual problem here is that the plist is being read from and written to two different locations. Somewhere in the app, there is code that reads the file into the array similar to this:
NSString *plistFavPath = [[NSBundle mainBundle] pathForResource:#"arrayFav"
ofType:#"plist"];
arrayFav = [[NSMutableArray alloc] initWithContentsOfFile:plistFavPath];
This logic reads the array from the application's bundle, which is a read-only location and part of the distributed app. Later when the edited array is persisted, code similar to this is used:
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES) objectAtIndex:0];
NSString *plistPath = [rootPath
stringByAppendingPathComponent:#"arrayFav.plist"];
NSLog(#"%# - %#", rootPath, plistPath);
[arrayFav writeToFile:plistPath atomically:YES];
The result here is that the updated file gets written to the app's documents directory, but it is never read from there, giving the appearance that the file is not being saved correctly. To correct this, you should change the code that reads the file to use the same path that you are writing to.
If you need to distribute a default version of the plist for use on the initial launch before the array has been edited, you could continue to include a version of the file in your bundle and then add code to your app delegate that check if the file exists in the documents directory and if it is not present, copies the bundle's default version of the file to the proper place.
[yourMutableArray writeToFile:fileName atomically:YES];
This should work. NSMutableArray inherits from NSArray which has a method to write to a plist.
writeToFile:atomically: won't work if your array contains custom objects.
If your array contains custom objects that are not Plist objects (NSArray, NSDictionary, NSString, NSNumber, etc), then you will not be able to use this method. This method only works on Plist objects.
Another option would be to use the NSCoding protocol, and write your objects to disk that way.
Yes
Look at the Property List Programming Guide.
phoneNumbers is a NSMutableArray
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
NSString *error;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"Data.plist"];
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects:
[NSArray arrayWithObjects: personName, phoneNumbers, nil]
forKeys:[NSArray arrayWithObjects: #"Name", #"Phones", nil]];
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];
if(plistData) {
[plistData writeToFile:plistPath atomically:YES];
}
else {
NSLog(error);
[error release];
}
return NSTerminateNow;
}

iPhone app loses file

I am writing an iPhone app – a client for some social network. The app support multiple accounts. Info about accounts are stored in a keyed archive.
A method used for saving:
- (void) saveAccounts {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:#"accounts.bin"];
// NSMutableArray *accounts = ...;
[NSKeyedArchiver archiveRootObject:accounts toFile:path];
}
A method uses for reading:
- (NSMutableArray *) loadAccounts {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:#"accounts.bin"];
NSMutableArray *restoredAccounts = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return [restoredAccounts retain];
}
The method saveAccounts is used only if some account is added/modified/deleted. The method loadAccounts is used every time the app starts. There isn't any other code that access this file.
I and one of my testers get an issue. At some moment the starts to act like accounts.bin is missing. If loadAccounts returns nil, the app offers to add an account. After I enter an account (the app must call saveAccounts), the app works normally, but when I launch it again, it asks me to add account again. The only solutions is too reinstall the app to iPhone, after reinstall it works for some time with any troubles.
We (I and my tester who get an issue) use iPhone 3G with 3.1.2.
An another tester who didn't experience this issue on his iPhone 3GS with 3.1.2.
Any ideas why this file disappears?
update
I found bug in my code. There was a code that deletes whole Document directory. Because this part of a code is a remote server related, it was hard to trace this code. Bug appeared under very rare conditions only.
Now the bug is found, and the code is corrected. wkw's answer didn't solved my problem, but it forced me to dig deeper. Thank you!
How about -- as a debugging device --verifying the contents of your Documents directory in loadAccounts or at least whenever the unarchiver returns nil. There's some code below to get the names of files in a directory. Set a breakpoint and just dump the NSArray to view the items.
If you can see that the file exists in the Docs dir, then your problem is elsewhere. Perhaps it did not successfully archive. check the return value on the call to archive the data:
if( ! [NSKeyedArchiver archiveRootObject:accounts toFile:path] ){
NSLog(#"Oooops! account data failed to write to disk");
}
Get names of files in directory:
- (NSArray*) directoryContentsNames:(NSString*) directoryPath {
NSArray* result;
{
result = [[NSFileManager defaultManager]
directoryContentsAtPath: directoryPath];
}
if( result && [result count] > 0 ){
NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];
for( NSString *name in result ){
if( ! [name isEqualToString:#".DS_Store"] )
[items addObject: name];
}
result = items;
}
return result;
}
Perhaps NSUserDefaults might be easier to use?
Also, is there a reason to use Keyed(Un)Archiver instead of NSArray's writeToFile?
if( ! [accounts writeToFile:path atomically:YES] )
; // do something since write failed
and to read the file in:
NSMutableArray *accounts = [[NSArray arrayWithContentsOfFile:path] mutableCopy];

Plist not saving from dictionary (to Documents)

I've been trying to save a plist of a NSDictionary to my app's Documents folder. I haven't tried this on the device yet but I'd like it to work on the simulator for testing purposes. The [self createDictionaryFromChoreList] method just creates a NSDictionary from some data in another class of mine. I've pretty much copied/pasted this code from the web documents and when I go to see if the file was saved or not, I find that it isn't. Here is the method block.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistName = [[NSString alloc] initWithFormat:#"%#chores.plist", self.firstName];
NSString *path = [documentsDirectory stringByAppendingPathComponent:plistName];
NSDictionary *choresDictionary = [[NSDictionary alloc] initWithDictionary:[self createDictionaryFromChoreList]];
[choresDictionary writeToFile:path atomically:YES];
Any help is greatly appreciated. Thanks in advance.
-S
You should also capture the BOOL returned by writeToFile:atomically:. That will tell you if the write succeeded or not.
Also, are you sure you are looking in the right documents folder? If you have more than one app in the simulator its easy to open the wrong app's documents folder in the Finder. I did that once and it cost me a couple of hours of frustration.
Edit01:
writeToFile:atomically: returning false explains why no file exist. The simplest explanation is that something in the dictionary is not a property list object.
From the NSDictionary docs:
This method recursively validates that
all the contained objects are property
list objects (instances of NSData,
NSDate, NSNumber, NSString, NSArray,
or NSDictionary) before writing out
the file, and returns NO if all the
objects are not property list objects,
since the resultant file would not be
a valid property list.
It just takes one non-plist object buried deep in a dictionary to prevent it from being converted to a plist.
Don't forget serialize the plist data:
Here is a snippet of code that I use for writing information to a plist
NSString *errorString;
NSData *data = [NSPropertyListSerialization dataFromPropertyList:plistDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorString];
[plistDict release];
if (!data) {
NSLog(#"error converting data: %#", errorString);
return NO;
}
if ([data writeToFile:[XEraseAppDelegate loadSessionPlist] atomically: YES]) {
return YES;
} else {
NSLog(#"couldn't write to new plist");
return NO;
}
This is something I whipped up really quickly and it correctly writes a plist directory of name and company to the documents directory. I have a feeling your dictionary creation method might have an issue. Try this out for yourself, then add your code and make sure it works.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *plistDirectory = [paths objectAtIndex:0];
NSString *plistPath = [plistDirectory stringByAppendingPathComponent:#"userCompany.plist"];
NSArray *userObjects = [[NSArray alloc] initWithObjects:#"Joe", #"Smith", #"Smith Co", nil];
NSArray *userKeys = [[NSArray alloc] initWithObjects:#"First Name", #"Last Name", #"Company", nil];
NSDictionary *userSettings = [[NSDictionary alloc] initWithObjects:userObjects forKeys:userKeys];
[userSettings writeToFile:plistPath atomically:YES];
Is it correct, that the name of file your writing to is:
SOEMTHINGchores.plist?
Created via:
NSString *plistName = [[NSString alloc] initWithFormat:#"%#chores.plist", self.firstName];
Also, what is the output of:
[choresDictionary print];
Some additional info would help to debug this.
Where exactly are you looking for the file?
I have the exact same code and it works fine for me.
Just that I have to dig deep to get the file. Something like:
/Users/myUserName/Library/Application Support/iPhone Simulator/User/Applications/0E62A607-8EEB-4970-B198-81CE4BDDB7AA/Documents/data.plist
And the HEX number in the path changes with every run. So I print the file path with every run.
Insert a break point at
NSDictionary *choresDictionary = [[NSDictionary alloc] initWithDictionary:[self createDictionaryFromChoreList]];
now when you step out drag your mouse over choresDictionary and check in the tooltip that its size is not 0x0 or you can simply do an NSLog of the choresDictionary
like NSLog(#"%#",choresDictionary); I think your dictionary has 0 key key value pairs thats why you are getting null into your documents folder.
Thanks,
Madhup
I was running into this issue as well. In my case it turned out that I was using NSNumbers for keys - which is not valid.