I am studying iPhone development and facing a problem with a reading/writing plist file. I followed an example from a iPhone development book but keep getting an error message when running.
The error message says : 2012-04-26 00:21:09.759 FileHandling[5915:207] -[__NSCFDictionary addObject:]: unrecognized selector sent to instance 0x685ac40
Here is the example code (it seems fine to me...though):
NSString *plistFileName = [[self documentPath] stringByAppendingPathComponent: #"Apps.plist"];
NSLog(#"Where is the file? => %#", plistFileName);
if ([[NSFileManager defaultManager] fileExistsAtPath:plistFileName]) {
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:plistFileName];
for (NSString *category in dict) {
NSLog(#"%#", category);
NSLog(#"=========");
NSArray *titles = [dict valueForKey:category];
for (NSString *title in titles) {
NSLog(#"%#", title);
}
}
} else {
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Apps" ofType: #"plist"];
NSLog(#"%#", plistPath);
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile: plistPath];
NSLog(#"Let's take a look : %#", dict);
NSMutableDictionary *copyOfDict = [dict mutableCopy];
NSLog(#"Let's look at the mutable dictationary : %#", copyOfDict);
NSArray *categoriesArray = [[copyOfDict allKeys] sortedArrayUsingSelector: #selector(compare:)];
for (NSString *cateogry in categoriesArray) {
NSArray *titles = [dict valueForKey: cateogry];
NSMutableArray *mutableTitles = [titles mutableCopy];
[mutableTitles addObject: #"New App Title"];
[copyOfDict setObject: mutableTitles forKey:cateogry];
}
NSString *fileName = [[self documentPath] stringByAppendingPathComponent: #"Apps.plist"];
[copyOfDict writeToFile: fileName atomically:YES];
}
According to the error message, the problem is occurring in the call to addObject: on an __NSCFDictionary. This means that, at runtime, a dictionary received a message to add an object.
However, in this code snippet, addObject: is apparently being sent to an NSMutableArray. This probably means that each object titles you're retrieving from dict in the last for-loop is not an array, but in fact another dictionary, that your code is simply referring to as an array.
Indeed, your code does seem well-formed, so check the well-formedness of your source plist; open it up in a plain text editor. Also, you use a ton of logging, so confirm this way: in the output, dictionaries (including the root entry) are denoted by {curly = braces}, where arrays are denoted by (round parentheses).
Related
I have a very simple xml file by name options.xml
<Dat>
<Name>Tom</Name>
<Option>1</Option>
</Dat>
Using NSXML I am trying to change "Tom" to "Jim" and save the file. How can I do that. I read many document and there is no straight forward solution. Can some one help me with the code ?
update: I ended up in trying with Gdatasxml
-(void)saveToXML
{
NSString* path = [[NSBundle mainBundle] pathForResource:#"options" ofType:#"xml"];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:path];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
GDataXMLElement *rootElement = [GDataXMLElement elementWithName:#"Dat"];
NSArray *mySettings = [doc.rootElement elementsForName:#"Dat"];
for (GDataXMLElement *mySet in mySettings)
{
NSString *name;
NSArray *names = [mySet elementsForName:#"Name"];
if (names.count > 0)
{
GDataXMLElement *childElement = (GDataXMLElement *) [names objectAtIndex:0];
name = childElement.stringValue;
NSLog(childElement.stringValue);
[childElement setStringValue:#"Jim"];
}
}
[xmlData writeToFile:path atomically:YES];
}
But this is not saving the data. Help.
Editing XML is a little difficult in iOS. You need to parse the original xml to a model and then form the xml.
You can make use of 3rd party library such as GDataXML for forming XML from a data source.
//Edited user info saved in a dictionary
NSDictionary *dictionary = #{#"Name": #"Jim", #"Option":#"1"};
GDataXMLElement *rootElement = [GDataXMLElement elementWithName:#"Dat"];
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
GDataXMLElement *element = [GDataXMLElement elementWithName:key stringValue:obj];
[rootElement addChild:element];
}];
//xml document is formed
GDataXMLDocument *document = [[GDataXMLDocument alloc]
initWithRootElement:rootElement];
NSData *xmlData = document.XMLData;
NSString *filePath = [self savedXMLPath];
//XML Data is written back to a filePath
[xmlData writeToFile:filePath atomically:YES];
Create a class that is essentially an XML "node". Then in your parser setup a system of these XML nodes in the same fashion as you read them. Then search through that body and find the element that you would like to change. Change it. Then write a function that goes through these "node" objects and writes a new NSString in XML format and save that string to file. There is no real easy way that I know of to write XML files. I'm sure someone has a library out there to do it, but I had very complex XML's to deal with so I wrote my own. If you would like specific code let me know and I can try to give you parts of what you may need.
You Can use GDATAXML for changing XML node
Here is Working Code snippet
NSString *XMLString = #"<Dat><Name>Tom</Name><Option>1</Option></Dat>";
NSError *error = nil;
GDataXMLElement *newElement = [[GDataXMLElement alloc] initWithXMLString: XMLString error: &error];
NSLog(#"New element: %# error: %#", newElement, error);
if(nil == error)
{
GDataXMLElement *childElement = [[newElement elementsForName: #"Name"] objectAtIndex: 0];
[childElement setStringValue:#"Jim"];
childElement = [[newElement elementsForName: #"Option"] objectAtIndex: 0];
[childElement setStringValue:#"2"];
}
NSLog(#"New element now: %#", newElement);
Check by using this code snippet
- (void)viewDidLoad
{
[super viewDidLoad];
if ([[NSFileManager defaultManager] fileExistsAtPath:pathString])
{
infoDict = [[NSMutableDictionary alloc] initWithContentsOfFile:pathString];
}
else
{
infoDict = [[NSMutableDictionary alloc]initWithObjects:[NSArray arrayWithObjects:#"BeginFrame",#"EndFrame", nil] forKeys:[NSArray arrayWithObjects:[NSNumber numberWithBool:YES],[NSNumber numberWithBool:YES], nil]];
if ([infoDict writeToFile:pathString atomically:YES])
{
NSLog(#"Created");
}
else
{
NSLog(#"Is not created");
NSLog(#"Path %#",pathString);
}
}
This is my code. I check if file is created, if not - I create a NSMutableDictionary and I write it to file at path, but writeToFile method returns NO. Where is problem? If I create this file with NSFileManager it works, but doesn't when I want to write a dictionary.
writeToFile:atomically only works if the dictionary you call it on is a valid property list object (see docs).
For a NSDictionary to be a valid property list object, among other things, its keys must be strings, but in your example the keys are NSNumber instances.
You can not control the content you are going to write sometimes. For example, you can't avoid a null value when you are going to write a JSON object that is gotten from a server.
NSData is compatible with these "invalid" values, so converting NSArray or NSDictionary to NSData is an ideal way in these cases.
write:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:jsonObject];
[data writeToFile:path atomically:YES];
read:
NSData *data = [NSData dataWithContentsOfFile:path];
NSDictionary *jsonObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
I am having troubles with my class which reads and writes data to a plist. Here is some code:
This first chunk is from my custom class with all my plist read and write methods.
-(NSString *) dataFilePath{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [path objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"userInformation.plist"];
}
-(bool)readUserIsMale{
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSDictionary *boolDict = [[NSDictionary alloc] initWithContentsOfFile:[self dataFilePath]];
return [[boolDict objectForKey:#"boolUserIsMale"] boolValue];
}
return nil;
}
-(void)writeUserIsMale:(bool)boolValue{
NSDictionary *boolDict = [[NSDictionary alloc] init];
[boolDict setValue:[NSNumber numberWithBool:boolValue] forKey:#"boolUserIsMale"];
[boolDict writeToFile:[self dataFilePath] atomically:YES];
}
I then in another class where desired import, create and use the class methods:
#import "plistReadWrite.h"
plistReadWrite *readWrite;
If I try and see its value in the console I get (null) return.
NSLog(#"%#",[readWrite readUserIsMale]);
This is of course after I have written some data like so:
[readWrite writeUserIsMale:isUserMale];
isUserMale being a bool value.
Any help would be massively appreciated, if you need anymore info let me know. Thanks.
I think this is mostly correct. In your writeUserIsMale: method you want a mutable dictionary, so you can actually set that key (this should have crashed for you as is, so I'm guessing a copy/paste problem?)
//NSDictionary *boolDict = [[NSDictionary alloc] init];
//should be:
NSMutableDictionary *boolDict = [[NSMutableDictionary alloc] init];
And then when you log the value, remember that bool (or BOOL) are primitives, not objects so:
NSLog (#"%d",[readWrite readUserIsMale]); // Will print 0 or 1
// or if you prefer:
NSLog (#"%#", ([readWrite readUserIsMale]? #"YES":#"NO")); // print YES or NO
Lastly, since this is objective-c, I would probably use BOOL instead of bool.
I'm assuming this is just a simple example, and that you know about NSUserDefaults for this sort of thing.
Hope that helps.
i am trying to parse a text file saved in doc dir below show is the code for it
NSArray *filePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *docDirPath=[filePaths objectAtIndex:0];
NSString *filePath=[docDirPath stringByAppendingPathComponent:#"SKU.txt"];
NSError *error;
NSString *fileContents=[NSString stringWithContentsOfFile:filePath];
NSLog(#"fileContents---%#",fileContents);
if(!fileContents)
NSLog(#"error in reading file----%#",error);
NSArray *values=[fileContents componentsSeparatedByString:#"\n"];
NSLog(#"values-----%#",values);
NSMutableArray *parsedValues=[[NSMutableArray alloc]init];
for(int i=0;i<[values count];i++){
NSString *lineStr=[values objectAtIndex:i];
NSLog(#"linestr---%#",lineStr);
NSMutableDictionary *valuesDic=[[NSMutableDictionary alloc]init];
NSArray *seperatedValues=[[NSArray alloc]init];
seperatedValues=[lineStr componentsSeparatedByString:#","];
NSLog(#"seperatedvalues---%#",seperatedValues);
[valuesDic setObject:seperatedValues forKey:[seperatedValues objectAtIndex:0]];
NSLog(#"valuesDic---%#",valuesDic);
[parsedValues addObject:valuesDic];
[seperatedValues release];
[valuesDic release];
}
NSLog(#"parsedValues----%#",parsedValues);
NSMutableDictionary *result;
result=[parsedValues objectAtIndex:1];
NSLog(#"res----%#",[result objectForKey:#"WALM-FT"]);
The problem what i am facing is when i try to print lineStr ie the data of the text file it is printing as a single string so i could not able to get the contents in line by line way please help me solve this issue.
Instead use:
- (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)separator
it covers several different newline characters.
Example:
NSArray *values = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *lineStr in values) {
// Parsing code here
}
ALso seperatedValues is over released. First one is created with alloc init, then on the next line it is replaced by the method componentsSeparatedByString. So the first one od lost without being released, that is a leak. Later the seperatedValues created by componentsSeparatedByString is released but it is already auto released by componentsSeparatedByString to that is an over release;
Solve all the retain/release/autorelease problem with ARC (Automatic Reference Counting).
Here is a version that uses convenience methods and omits over release:
NSArray *values = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *lineStr in values) {
NSArray *seperatedValues = [lineStr componentsSeparatedByString:#","];
NSString *key = [seperatedValues objectAtIndex:0];
NSDictionary *valuesDic = [NSDictionary dictionaryWithObject:seperatedValues forKey:key];
[parsedValues addObject:valuesDic];
}
NSLog(#"parsedValues---%#",parsedValues);
Are you sure the line separator used in your text file is \n and not \r (or \r\n)?
The problem may come from this, explaining why you don't manage to split the files into different lines.
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;
}