So I've read a ton of SO-questions about plists and how to save to them, and although I don't know, why no iPhone-Dev-Book I've seen so far covered this (they all used the tableView editing function), I managed to REALLY write to a plist by copying it to the documents folder like this:
pListPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
pListPath = [pListPath stringByAppendingPathComponent:#"piggyBanks.plist"];
NSLog(#"Path: %#", pListPath);
// if the file does not exist yet, create it, and copy the plist data into it, that can be found in the bundle
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:pListPath]) {
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"piggyBanks" ofType:#"plist"];
[fileManager copyItemAtPath:sourcePath toPath:pListPath error:nil];
}
// make the plist content available for usage
pListContent = [[NSMutableDictionary alloc] initWithContentsOfFile:pListPath];
NSLog(#"pListContent: %#", pListContent);
So far so good, but now, if I wanna change some plist value when a user taps on a tableViewCell (it's a custom one, if that's important), although pListPath, pListContent and others are properties, defined in .h and synthesized in .m, I have to redefine pListPath and pListContent inside didSelectRowAtIndexPath, to get the path to be known in that moment.
Could someone please tell me why? I mean, it's nice, that it works, but I'd like to know, why it has to be like that, or if I did a mistake somewhere else..
Thanks!
If plistPath is a string property of your class, you need to assign it as such:
if (!self.pListPath)
{
NSString *newPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
newPath = [newPath stringByAppendingPathComponent:#"piggyBanks.plist"];
self.pListPath = newPath;
}
Then afterwards, use self.pListPath instead of pListPath.
You are having to re-set it because at the moment, pListPath is being assigned to an autoreleased string which will have been removed by the time you need it again. Setting the property (assuming the property is retained or copied) will retain the string for you.
I googled for class variables in objective C, and found my way through various articles, till i found this blog/blogpost, which explains the self. and _underscore thing really well!
I now always declare my Ivars with an underscore, the properties without. Works out quite well. And to sum it up, why to ALWAYS use self.yourPropertiesName, you are calling a method (setter and getter)! And like any other method you are calling, you need to say, who is calling.
Hope this will help someone else too :)
Related
i added a key called "App" to my AppName-Info.plist manually , i can get the value from it by calling this code
NSBundle *mainBundle;
mainBundle = [NSBundle mainBundle];
NSString *value = [mainBundle objectForInfoDictionaryKey:#"App"];
NSLog(#"App: %#",value);
But what i couldn't do is changing the value with any code.. is it possible ? and if yes how can it be done ?
thanks for help in advance :)
You should consider NSUserDefaults or if you want to modify a bundled .plist try this.
NSString* plistFilePath = nil;
NSFileManager* manager = [NSFileManager defaultManager];
if ((plistFilePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"mySpecial/PathTo.plist"]))
{
if ([manager isWritableFileAtPath:plistFilePath])
{
NSMutableDictionary* infoDictioio = [NSMutableDictionary dictionaryWithContentsOfFile:plistFilePath];
[infoDictio setObject:#"foo object" forKey:#"fookey"];
[infoDictio writeToFile:plistFilePath atomically:NO];
[manager setAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] ofItemAtPath:[[NSBundle mainBundle] bundlePath] error:nil];
}
}
You should not modify your apps Info.plist file (or anything in your app's bundle) at runtime. This is bad practice and will also break your bundles code signature (which will result in not being able to launch the app anymore!).
If you want to store settings you should have a look at NSUserDefaults.
It's not possible.
By default the AppName-Info.plist isn't copied into the bundle in the Copy Bundle Resources phase of the build.
So if you want to have a plist which you can write to an option would be to create it at run time in the temporary files location and read/write to it there.
This is a great place to research how to do it.
My NSMutableArray is from the NSCachesDirectory,I'm recreating/reloading the array in every VIEW. I'm displaying/preview it in UIScrollView which can be deleted in VIEW_A. In another ViewController VIEW_B, I have another preview of it, for another purpose.
What I needed is when I delete the image in VIEW_A, I will be able to determine in VIEW_B the deleted images or index. Because I'm using their indexes in VIEW_B. How can I be able to do it. I'm thinking of saving it all in NSUserDefaults but how.
Delete method:
[button removeFromSuperview];
[_buttons removeObjectAtIndex:button.tag];
[_images removeObjectAtIndex:button.tag];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"oneSlotImages%u.png", button.tag]];
[fileManager removeItemAtPath: fullPath error:NULL];
I just want to know/determine that indexes that are deleted from other view.
I'm not sure if i really understood your problem, but here's my response:
If you need to update some object after something change (like your array). You should use a pattern observer. You can get free one with NSNotificationCenter (in foundation lib), but i discourage it. You can use kvo/kvc but it's not as clean as if you'll do in a pattern observer.
By the way, you should use only one array, and if you need to perform change on it, use a specific controller to do it. (Not from the client side like in your UIViewA/B)
here on wiki how to use it (in java, but the skeleton is the same):
http://en.wikipedia.org/wiki/Observer_pattern
By the way NSUserDefault should be only used to store user preferences, not some app logic values.
Best option is to use a delegate method.
In VIEW_B set up a delegate protocol and in the delete method have a call to something like...
[self.delegate didDeleteImageAtIndex:button.tag];
then in VIEW_A before you push VIEW_B set it up as the delegate.
Then in VIEW_A have the method...
- (void)didDeleteImageAtIndex:(int)index
{
//delete image from VIEW_A's array
}
I am currently creating a controller class for my plist.in this plist I have a root dictionary that has several types in it (Number, String and Dictionary), In my controller class I check for a plist then add it to the documents so I can read and write to it.
From here I read whats in my current plist and pass those values over to tempvars I have set up in this class.
This is what my read method looks like in my plist controller class.
-(void) readPlistData
{
// Data.plist code
// 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 *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.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:#"EngineProperties" 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 liost 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);
}
// assign values
self.protocolSignature = [temp objectForKey:#"Protocol"];
self.requestNumber = [temp objectForKey:#"RequestNumber"];
//How do I add the dictionary values here?
}
The reason I put the data into variables is because latter I am going to use these values to test against checks I want to perform against my db.. making sure of things like i am receiving the correct request number etc.
UPDATE:: my idea to add them to the dictionary inside the root dictionary would be something like this. which i think is not even close but it might give you a better clue to what I am trying to do.
self.cacheValue = [temp objectForKey:#"Cache Value"];
self.manufacturers = [cacheValue objectForKey:#"Manufacturers"];
self.models = [cacheValue objectForKey:#"Model"];
self.subModels = [cacheValue objectForKey:#"SubModels"];
any help would be greatly appreciated.
I believe you want to do the following:
Define your cacheValue property in the .h as a mutable dictionary.
NSMutableDictionary *cacheValue;
Serialize the plistXml as a NSMutableDictionary:
// This is the root Dictionary
NSMutableDictionary *temp = (NSMutableDictionary *)[NSPropertyListSerialization propertyListWithData:plistXML options:NSPropertyListMutableContainersAndLeaves format:NSPropertyListXMLFormat_v1_0 error:&error];
Since everything is mutable, you can now read, update, insert, delete any part of the dictionary or its subcontents. For instance, grabbing the Mutable Dictionary "Cache Value" is just:
self.cacheValue = [temp objectForKey:#"Cache Value"];
Remember to check that the object is not nil in case there isn't a value for the key. The key needs to be exactly as it appears in the plist.
Updating a value in the Mutable Dictionary is easy:
[self.cache setValue:#"New Value" forKey:#"Sub"];
And finally, to save the changes in the root Mutable Dictionary back to the plist:
/*
The flag "atomically" specifies whether the file should be written atomically or not.
If flag is YES, the receiver is written to an auxiliary file, and then the auxiliary file is renamed to path.
If flag is NO, the dictionary is written directly to path.
The YES option guarantees that path will not be corrupted even if the system crashes during writing.
*/
[self.temp writeToFile:plistPath atomically:YES];
Hope this helps, cheers!
I have a plist with an array at top level and then a number of items within it.
When I try the following
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"Spots.plist"];
spotsArray = [[NSMutableArray arrayWithContentsOfFile:finalPath] retain];
the array spotsArray is empty.
I have tried a number of things and have used plists successfully before. I dont know what the issue is now.
What could be causing the issue, my plist looks like this
It's not an array at the top level it's a dictionary with a single item called 'New Item'.
Try NSMutableArray *mutableArray = [[[NSDictionary dictionaryWithContentsOfFile:finalPath] objectForKey:#"New Item"] mutableCopy] autorelease]
This should mean that your .plist file does not exist or not readable.
Try to create the file with writeToFile at the same path to verify it works. (It can also help you to verify the directory)
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.