Saving a NSArray - objective-c

I would like to save an NSArray either as a file or possibly use user defaults. Here's what I am hoping to do.
Retrieve already saved NSArray (if any).
Do something with it.
Erase saved data (if any).
Save the NSArray.
Is this possible, and if so how should I do this?

NSArray provides you with two methods to do exactly what you want: initWithContentsOfFile: and writeToFile:atomically:
A short example might look like this:
//Creating a file path under iOS:
//1) Search for the app's documents directory (copy+paste from Documentation)
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//2) Create the full file path by appending the desired file name
NSString *yourArrayFileName = [documentsDirectory stringByAppendingPathComponent:#"example.dat"];
//Load the array
NSMutableArray *yourArray = [[NSMutableArray alloc] initWithContentsOfFile: yourArrayFileName];
if(yourArray == nil)
{
//Array file didn't exist... create a new one
yourArray = [[NSMutableArray alloc] initWithCapacity:10];
//Fill with default values
}
...
//Use the content
...
//Save the array
[yourArray writeToFile:yourArrayFileName atomically:YES];

You could implement NSCoding on the objects the array contains and use NSKeyedArchiver to serialize/deserialize your array to disk.
BOOL result = [NSKeyedArchiver archiveRootObject:myArray toFile:path];
The archiver will defer to your NSCoding implementation to get serializable values from each object and write a file that can be read with NSKeyedUnarchiver:
id myArray = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
More info in the serialization guide.

This would seem to be a problem most suited to Core Data as this will deal with all the persistent object data. When you retrieve you data it will return an NSSet, which is unsorted so you will have to have some way of sorting the data in the array such as a unique id number assocaited with each object you create.

Related

Changing values in plist - Doesn't change value

I am using this simple method I found to write to a plist but it doesn't seem to work:
-(IBAction)modifyPlist:(id)sender{
NSBundle *mainBundle=[NSBundle mainBundle];
NSString *path=[mainBundle pathForResource:#"Preferences" ofType:#"plist"];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
[[dict objectForKey:#"Root"] setBool:YES forKey:#"startup"];
}
What am I doing wrong?
Thanks
You're loading the file into memory, and modifying that memory, but you're not writing it back to disk. You'll want to use NSDictionary's -writeToFile:atomically:
[dict writeToFile:path atomically:YES];
Reference link here.
I hope you know exactly what you're doing and this is (for whatever reason) your design decision -
because you're about to write to your app's bundle.
That'll certainly fail under Sandboxing and is generally a bad, bad idea.
If you've after storing user defaults please read up on the Mac way of storing preferences:
Preferences and Settings Programming Guide
// Make a path to the plist in the Documents directory (not the bundle directory)
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Preferences.plist"];
// Then after creating or making changes to your dictionary, write your dictionary out to the plist file
[dict writeToFile:path atomically:YES];
Maybe look at:
Should I use NSUserDefaults or a plist to store data?

How to edit elements in plist file

I have a Settings.plist and I want to edit some values in this file.
My function to edit/writing is:
- (void) setParamWithName: (NSString*) Name withValue: (NSString*) Value {
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"Settings.plist"];
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property list into dictionary object
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
// checking if element exists, if yes overwriting
// if element not exists adding new element
[temp writeToFile:plistPath atomically:YES];
}
This function read and write (with te same values) Settings.plist.
I do not have any idea (my knowledge about objective-c is not enough) how to add new element or edit existing element. Can anyone help mi with this issue?
I think it's easier as you think.
Once you got the path of the file read it into a NSDictionary. Make a mutable copy of that dictionary with mutableCopy and NSMutableDictionary.
Now edit that mutable dictionary as you like (add s.th., remove s.th., edit s.th. and so on).
Now that you're done you can write it back to the old path as you did with temp.
Your main problem is that you're not working with a mutable version of that dicitionary. It'd make your life much easier.

My plist is changed, how to replace the changed strings in documents directory

My plist is an array with dictionaries.
At launch, the .plist is copied from bundle to documents directory if it doesn't exist there already.
But if the plist already exists in documents directory:
each dictionary must be checked against its updated twin dictionary in the bundle to look for changes in the "District" string.
And of course, replace the string if there has been made a change.
This is the copy plist function:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self copyPlist];
return YES;
}
- (void)copyPlist {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Wine.plist"];
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"Wine" ofType:#"plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path]) {
[fileManager copyItemAtPath:bundle toPath:path error:&error];
} else {
//I need to check if the "District" value has been changed in any of the dictionaries.
}
}
Any suggestion for how this could be done, or useful tutorials/sample codes?
My guess is that I have to extract the content of the plists into NSMutableArrays: bundleArray and documentsArray. Then find the matching dictionaries in the arrays. Could be done by looking at "Name" string to be equal. Then compare the two "District" strings in the matching dictionaries and look for any changes, and replace the changed ones. But I have no idea how it should be done so any help is very useful as this is very important!
I think your Dictionary Structure is as follows:
Root as "Array"
Dictionary 1 with "District as one of the key"
Dictionary 2 with "District as one of the key"
You can check if two NSDictionary at particular indexes of Array are equal or not, which I have coded below.
NSArray *bundleArray=[[NSArray alloc] initWithContentsOfFile:#"path to .plist in bundle"];;
NSArray *documentArray=[[NSArray alloc] initWithContentsOfFile:#"path to .plist in DocumentDirectory"];
BOOL updateDictionary=NO;
for(int i=0;i<bundleArray.count;i++){
NSDictionary *bundleDic=[bundleArray objectAtIndex:i];
NSDictionary *documentDic=[documentArray objectAtIndex:i];
if(![bundleDic isEqualToDictionary:documentDic])
{
/*
*if there is any change between two dictionaries.
* i.e bundle .plist has changed so update .plist in document Directory
*/
[documentDic setValue:[bundleDic objectForKey:#"District"] forKey:#"District"];
updateDictionary=YES;
}
}
//Update Dictionary
if(updateDictionary){
[documentArray writeToFile:#"path to .plist in DocumentDirectory" atomically:YES];
}

How to write data in .plist?

I'm trying to save some comments in a plist, that's OK cause its just a prototype. The problem is that i can read from plist but when I try to write and read after that, it throws an "array out of bounds" exception. I can't figure it out what I'm doing wrong here.
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Comments" ofType:#"plist"];
NSMutableArray *plistArray = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
NSMutableDictionary *newComment = [NSMutableDictionary dictionary];
[newComment setValue:commentTitle.text forKey:#"title"];
[newComment setValue:comment forKey:#"comment"];
[plistArray addObject:newComment];
[plistArray writeToFile:filePath atomically:NO];
That works fine, then i try to read:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Comments" ofType:#"plist"];
NSMutableArray *plistArray = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
NSMutableDictionary *dictionary = (NSMutableDictionary *) [plistArray objectAtIndex:0];
NSLog(#"%#", [dictionary objectForKey:#"title"]);
And it throws the exception.
If I add the item manually to the plist, it works fine, i guess it means that my reading code its fine.
Could it be the structure of my plist?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
</array>
</plist>
* Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFArray objectAtIndex:]: index (1) beyond bounds (1)'
I added the "description" to the array before writing to the plist. If i use the following code:
NSString *aDocumentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// NSString *aFilePath = [NSString stringWithFormat:#"%#/Comments.plist", aDocumentsDirectory];
//
// NSMutableArray *plistArray = [[NSMutableArray alloc] initWithContentsOfFile:aFilePath];
The return is (null)
But if i use:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Comments" ofType:#"plist"];
NSMutableArray *plistArray = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
i can see the contents of the array, and its all working properly.
The problem is: In both ways i cant write to the file, it keeps returning "NO". And i already checked the permissions
You are trying to write the file into mainBundle. Definitely not possible.
You will have to write the plist file to Documents or Application Support folder of the app.
Create File Path in Documents Directory :
NSString *aDocumentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *aFilePath = [NSString stringWithFormat:#"%#/Comments.plist", aDocumentsDirectory];
Write to FilePath
[plistArray writeToFile:aFilePath atomically:YES];
Read From FilePath
NSMutableArray *plistArray = [[NSMutableArray alloc] initWithContentsOfFile:aFilePath];
I see two problems with your code:
(May or may not be a problem). If the file does not exist initially, the initWithContentsOfFile: selector will return nil, causing the rest of your code to be no-ops.
(Probably the cause). You may not write to the bundle resources directory. Store your file in the Documents or Caches directory instead.
To locate your documents directory, use something like this:
- (NSString*) pathForDocument:(NSString*)documentName {
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if(documentDirectories.count < 1) return nil;
return [[documentDirectories objectAtIndex:0] stringByAppendingPathComponent:documentName];
}
First of all, why are you writing a file into your bundle?
Then, to address your problem, check if you actually did write the file.
if ([plistArray writeToFile:filePath atomically:NO])
NSLog (#"Written");
else
NSLog (#"Not Written");
Also, log your array when you're read it using -(void)description to check the contents of the dictionary.
Edit
As you said that you're not writing to your plist. For now, just create a test plist on your desktop.
NSString *testPath = [[NSString stringWithString:#"~/Desktop/Comments.plist"] stringByExpandingTildeInPath];
if ([plistArray writeToFile:testPath atomically:NO])
NSLog (#"Written");
else
NSLog (#"Not Written");
If that still returns Not Written, then there's something wrong with your dictionary. Which I doubt because it's just strings (Though they could be placeholders for asking your question on stackoverflow. The docs states that the classes in the dictionary must be of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary). If that says written though, I'm guessing it doesn't write to your bundle because of permissions, which then you have to change your plist location to somewhere else other than your bundle, which I highly recommend.
If you only put one item in the array, you should obviously use index 0 instead of 1 when reading from it:
NSMutableDictionary *dictionary = (NSMutableDictionary *) [plistArray objectAtIndex:0];

NSMutableArray. Object value is not being replaced

I have a NSMutableArray and I want to change the value of a particular object.
However, it's not changing.
Below is my code:
//create two array to store data later
NSMutableArray *feelingsArray = [[NSMutableArray alloc] init];
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *feelingsPath = [documentsPath stringByAppendingPathComponent:#"Feeling.plist"];
//This copies objects of plist to array if there is one
[feelingsArray addObjectsFromArray:[NSArray arrayWithContentsOfFile:feelingsPath]];
//replace object at particular location
[datesArray replaceObjectAtIndex:number withObject:feeling.text];
Do I need to make any changes or add some code like synchronise etc.?
Please provide some guidance.
Shouldn't
[datesArray replaceObjectAtIndex:number withObject:feeling.text];
be
[feelingsArray replaceObjectAtIndex:number withObject:feeling.text];
?