write to plist - error [duplicate] - ios

I have created save.plist in a resource folder. I have written some data within that directly (without using coding). I am able to read that data but I'm not able to write through code to the same save.plist. By using following code I am trying to write the data but it gets stored within my .app plist.
The code is here
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"save" ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSMutableDictionary *temp = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
if (!temp) {
NSLog(errorDesc);
[errorDesc release];
}
// [temp setValue:#"123" forKey:#"line1"];
// [temp writeToFile:plistPath atomically: YES];
//Reading data from save.plist
NSLog([temp objectForKey:#"name"]);
NSLog([temp objectForKey:#"wish"]);
NSNumber *num=[temp valueForKey:#"roll"];
int i=[num intValue];
printf("%d",i);
//writitng the data in save.plist
[temp setValue:#"green" forKey:#"color"];
[temp writeToFile:plistPath atomically: NO];
NSMutableDictionary *temp1 = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
NSLog([temp objectForKey:#"color"]);
I want that, the data which I want to write should get written into save.plist only which is stored in references. I am new with this concept. So if anyone knows it please help me.
Thanks in advance.
:-)

I don't know if I understand your question, but if you want to write into a .plist within your .app bundle you are probably doing something wrong. If you want to store preferences, you should consider using NSUserDefaults.
If you really want to modify a bundled .plist - here is some code:
NSString *plistPath = nil;
NSFileManager *manager = [NSFileManager defaultManager];
if (plistPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"Contents/Info.plist"])
{
if ([manager isWritableFileAtPath:plistPath])
{
NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[infoDict setObject:[NSNumber numberWithBool:hidden] forKey:#"LSUIElement"];
[infoDict writeToFile:plistPath atomically:NO];
[manager changeFileAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] atPath: [[NSBundle mainBundle] bundlePath]];
}
}
Update:
Nate Flink pointed out that some of the NSFileManager methods used above are deprecated.
He posted an answer with the replacement methods below:
https://stackoverflow.com/a/12428472/100848

Updated version of the original awesome example by weichsel (thank you!). Xcode threw a couple warnings one of which is a deprecated method on NSFileManager. Updated here with non-deprecated methods from iOS 5.1
NSString *plistPath = nil;
NSFileManager *manager = [NSFileManager defaultManager];
if ((plistPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"mySpecial/PathTo.plist"]))
{
if ([manager isWritableFileAtPath:plistPath])
{
NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[infoDict setObject:#"foo object" forKey:#"fookey"];
[infoDict writeToFile:plistPath atomically:NO];
[manager setAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] ofItemAtPath:[[NSBundle mainBundle] bundlePath] error:nil];
}
}

When you build the app, it will create an executable file "appName.app" and all the files are built in the bundle. Therefore, you can't access to resource folder when the app is running because all the data is in the bundle(not in folder).
However, you can access to a temp folder which contains some information of the app.
You can find the temp folder here:
Open finder--click on your username(under PLACES)--Library--Application Support--iPhone Simulator--User--Applications--(here you can find all the temp folders of your iPhone apps)
You can access to this temp folder by:
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
If you name your file save.plist, you can access to it like this:
NSString *filePath = [documentsDirectory stringByAppendingString:#"_save.plist"];
Then you just save your file to this filePath and it will appear in the temp folder named "Documents_save.plist".
*Note that the temp folder's name varies every time you run the app.
Recommend a book for you: 《Beginning iPhone Development--Exploring the iPhone SDK》. In Chapter 11 you can find what you want.

To summarize some of the other answers:
You're problem is that you're trying to write the file back into the folder that contains your application. That folder is not writable at runtime. Everything you're doing is fine, you just need to pick a different location to write your file to.
You can use the NSSearchPathForDirectoriesInDomains function to find a more suitable folder for this data. (Such as the #"Documents" folder.)

try this:
-(void)add:(NSRunningApplication *) app {
if ([self contains:app]) return;
[self.apps addObject:app.localizedName];
[self.apps writeToFile:self.dataFile atomically:YES];
}
from "Cocoa Programming".

you have to copy your plist into document directory...
because you cannot save anything without saving into document file....when you copied it will allow to write/modify on plist

Related

Will property list data persist after killing the application?

I have created a custom property list file. The file is stored in the application document.
While user login is successful the login information is stored in the plist, and it is working fine.
The plist content are cleared while log out, this also works fine.
When i am still login i killed the application. When the app opens the plist data i cleared.
code used to save to file:
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"xxxxPlist.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
plistPath = [[NSBundle mainBundle] pathForResource:#"xxxxPlist" ofType:#"plist"];
}
dict=[[self cleanDictionary:[dict mutableCopy]] mutableCopy];
NSDictionary *plistDict=[[NSDictionary alloc] initWithObjectsAndKeys:dict,#"login_data", nil];
NSError *error = nil;
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 options:NSPropertyListImmutable error:&error];
if(plistData)
{
[plistData writeToFile:plistPath atomically:YES];
}
else
{
//error here
NSLog(#"%# ",error);
}
code used to fetch data
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"xxxx.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
plistPath = [[NSBundle mainBundle] pathForResource:#"xxxxPlist" ofType:#"plist"];
}
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
return [dict objectForKey:#"login_data"];
I there any way out to persist the data?
There are several things that may be causing problems
When saving to file
1) My understanding is that you specifically want to save to /Documents folder specifically to ensure your file persists
2) So you correctly build following path
"/Documents/xxxxPlist.plist"
3) But then why do you check if a file already exists at that location?
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
plistPath = [[NSBundle mainBundle] pathForResource:#"xxxxPlist" ofType:#"plist"];
}
You just have to write to the path when you are ready.
If there is an old file at this location it will be overwritten.
And my understanding is this is the wanted behaviour, because you've already read that file and the data is in that dictionary "dict".
4) Also, by asking NSBundle to give you path for your file name
"xxxxPlist.plist"
there is a risk that it will just give you back some other path with file named the same (not in /Documents) if such file happens to exist. For example if you happened to write to say, /Cache folder earlier (with different code), your app will keep getting the /Cache path and keep reading/writong there (not in /Documents). And with the existing code you would have gotten nil here for path on the very first run, so not sure how he file got created in the first place.
5) Then I am not sure what exactly does this line
dict=[[self cleanDictionary:[dict mutableCopy]] mutableCopy];
Why first make a mutable copy, then presumably get immutable copy back and get a mutable one of it. Can't -cleanDictionary: just return the same mutable copy it was passed?
When reading from file
1) Not sure why you're searching for a different file first?
"/Documents/xxxx.plist" not "/Documents/xxxxPlist.plist"
Also what happens if "xxxx.plist" exists, then you'll never get to "xxxxPlist.plist" that you are writing in the other section.
2) Then, yes, you have to check if a file exists at certain path before you try to read it. But, in your case, if it does not exist, you don't ask NSBundle for another location, because you need your specific file in /Documents, and you don't know what you'll get from NSBundle, if your file is not where it should be.
So if there is no "xxxxPlist.plist" file, it's just your first run of the app and you will be creating your initial dict.

Cant write data to plist file in - objective c

I'm trying to read and write data to my plist file, While the reading part goes well, the writing part does nothing.
I might be mistaken for the writing part - i can't see any changes in my file under my bundle - it is still empty after my changes, and when i close the app and open it again - i still see empty email address line.
The code for writing (and placing the plist in the document folder for future writings)
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"userData.plist"];
NSLog(#"plist path %#",destinationPath);
if ([fileManger fileExistsAtPath:destinationPath]){
NSLog(#"database localtion %#",destinationPath);
//return;
}
NSString *sourcePath = [[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"userData.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: sourcePath];
NSString *emailAddress = (NSString *)[dict objectForKey: #"emailAddress"];
if([emailAddress isEqualToString:#""])
{
// Do stuff
}
And for writing
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath =[pathsArray objectAtIndex:0];
NSString *destinationPath = [doumentDirectoryPath stringByAppendingPathComponent:#"userData.plist"];
NSMutableDictionary *plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile: destinationPath];
[plistDict setValue:#"myEmail#gmail.com" forKey:#"emailAddress"];
[plistDict writeToFile:destinationPath atomically: YES];
But as i said, nothing is changed in the file itself, and not even when i save, close the app and open it again (the string is always empty on my reading part)
Any help will be more than welcomed.

writing string to txt file in objective c

Pulling my hair out trying to work this out. i want to read and write a list of numbers to a txt file within my project. however [string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error] doesnt appear to write anything to the file. I can see there is the path string returns a file path so it seems to have found it, but just doesnt appear to write anything to the file.
+(void)WriteProductIdToWishList:(NSNumber*)productId {
for (NSString* s in [self GetProductsFromWishList]) {
if([s isEqualToString:[productId stringValue]]) {
//exists already
return;
}
}
NSString *string = [NSString stringWithFormat:#"%#:",productId]; // your string
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSError *error = nil;
[string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", error.localizedFailureReason);
// path to your .txt file
// Open output file in append mode:
}
EDIT: path shows as /var/mobile/Applications/CFC1ECEC-2A3D-457D-8BDF-639B79B13429/newAR.app/WishList.txt so does exist. But reading it back with:
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
returns nothing but an empty string.
You're trying to write to a location that is inside your application bundle, which cannot be modified as the bundle is read-only. You need to find a location (in your application's sandbox) that is writeable, and then you'll get the behavior you expect when you call string:WriteToFile:.
Often an application will read a resource from the bundle the first time it's run, copy said file to a suitable location (try the documents folder or temporary folder), and then proceed to modify the file.
So, for example, something along these lines:
// Path for original file in bundle..
NSString *originalPath = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSURL *originalURL = [NSURL URLWithString:originalPath];
// Destination for file that is writeable
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *documentsURL = [NSURL URLWithString:documentsDirectory];
NSString *fileNameComponent = [[originalPath pathComponents] lastObject];
NSURL *destinationURL = [documentsURL URLByAppendingPathComponent:fileNameComponent];
// Copy file to new location
NSError *anError;
[[NSFileManager defaultManager] copyItemAtURL:originalURL
toURL:destinationURL
error:&anError];
// Now you can write to the file....
NSString *string = [NSString stringWithFormat:#"%#:", yourString];
NSError *writeError = nil;
[string writeToFile:destinationURL atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", writeError.localizedFailureReason);
Moving forward (assuming you want to continue to modify the file over time), you'll need to evaluate if the file already exists in the user's document folder, making sure to only copy the file from the bundle when required (otherwise you'll overwrite your modified file with the original bundle copy every time).
To escape from all the hassle with writing to a file in a specific directory, use the NSUserDefaults class to store/retrieve a key-value pair. That way you'd still have hair when you're 64.

how to store an image path in a plist?

I know this is probably a silly question but I'm storing most of my game data in a plist - with that I'd like to include references to images used within my game - same hierarchal level as 'supporting files'. I have different types of images stored in 3 separate folders. One folder for example is called imageclue. How could I store the path in my plist, I'm stuck because I can't just store the path in my plist as string - filename.jpg. I've tried getting the path of the file but when I log it out it .
Sorry if I'm not explaining well and thank you in advance for any help :)
EDIT**
I have a plist file added to my program I don't want to programatically add to it as the images are constants - the screenshots below show a tutorial instead of the filename.jpg (because that won't work seen as my images are stored in a file) I wondered what path name do I use as a string.
The image is from a tutorial off of appcoda.com - where it says thumbnails are the image path files. If you look at where the images are stored on the left - they are stored with the program files. My images are in a folder in there so I'm confused as to what to enter in my plist for the image file.
Hope this clears up what I meant, sorry :)
Store three variables in .h file
#interface YourViewController : UIViewController
{
NSString *folder1;
NSString *folder2;
NSString *folder3;
}
in viewdidload:
-(void) viewdidLoad
{
//get the documents directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//getting the folder name:
folder1 = [NSString stringWithFormat:#"%#/imageclue",
documentsDirectory];
folder2 = [NSString stringWithFormat:#"%#/folder2",
documentsDirectory];
folder3 = [NSString stringWithFormat:#"%#/folder3",
documentsDirectory];
}
-(NSArray*) getPlistFromFolder:(NSString*)folder imageName:(NSString*)image
{
NSString *imageTitle = [NSString stringWithFormat:#"%#/image",
folder];
NSArray *data = [[NSArray alloc] initWithContentsOfFile:plistName];
return data;
}
So in the plist file, just store the image name.
Hope this helps...
Do it like this,
NSDictionary *imagePaths = #{#"image 1": [NSHomeDirectory() stringByAppendingPathComponent:#"image 1"]};
[self writeToPlist:imagePaths];
- (void)writeToPlist:imagePaths:(id)plist{
NSError *error;
NSData *data = [NSPropertyListSerialization dataWithPropertyList:plist format:kCFPropertyListXMLFormat_v1_0 options:0 error:&error];
if(error){
NSLog(#"Could not write to file");
return;
}
[data writeToFile:[self plistPath] atomically:YES];
}
Like wise loading is simple as this;
[self loadImagePathForImageNamed:#"image 1"];
- (NSString*)loadImagePathForImageNamed:(NSString*)imageName{
}
- (NSString*)loadImagePathForImageNamed:(NSString*)imageName{
NSData *data = [NSData dataWithContentsOfFile:[self plistPath]];
NSString *error;
NSPropertyListFormat format;
NSDictionary *dictionary = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
if(error){
NSLog(#"Could not open plist %#", error);
return nil;
}
return dictionary[imageName];
}
You may have to handle the error when the file is not there by creating a new one, otherwise this should work.
You are storing path right way, just need to store filename of image with extension in plist when your images are in your Application Bundle, for more reference you can define key name Instead "item1", "item2" in your plist.
Now coming to actual Question, how to access image from plist
Step 1 : Read your recipes.plist from Application Bundle
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"recipes" ofType:#"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:bundlePath];
Step 2 : Now Get Image/Thumbnails name out of it, which you want to load
Step 3 : Define following Function in your Controller, which returns image from name
- (UIImage *)getImageWithName:(NSString *)imageFileName
{
NSString *ext = [imageFileName pathExtension];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:[imageFileName stringByDeletingPathExtension] ofType:ext];
return [UIImage imageWithContentsOfFile:imagePath];
}
HOW TO USE
Suppose you want to load Image with key "Item2" then write following code
NSString *imageFileName = [[dict objectForKey:#"Thumbnail"] valueForKey:#"Item2"];
UIImage *item2Image = [self getImageWithName:imageFileName];
For "Item6"
NSString *imageFileName1 = [[dict objectForKey:#"Thumbnail"] valueForKey:#"Item6"];
UIImage *item6Image = [self getImageWithName:imageFileName1];

.plist path on iOS device

-(void)login{
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:#"login" ofType:#"plist"];
NSMutableDictionary* plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
[plistDict setObject:#"si" forKey:#"stato"];
[plistDict writeToFile:path atomically: YES];
}
In iOS Simulator the plist has been correctly written, but when I try to write the .plist on my iPhone, it doesn't work. I guess it is because of the wrong .plist path.
Do the iOS devices use different path?
First you have to check if the file exits in your documents directory. If it doesn't exits there then you can copy it to the document directory. You can do it this way
-(void)login{
BOOL doesExist;
NSError *error;
NSString *filePath= [[NSBundle mainBundle] pathForResource:#"login" ofType:#"plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString * path =[[NSString alloc] initWithString:[documentsDirectory stringByAppendingPathComponent:#"login.plist"]];
doesExist= [fileManager fileExistsAtPath:path];
if (doesExist) {
NSMutableDictionary* plistDict=[[NSMutableDictionary alloc] initWithContentsOfFile:path];
}
else
{
doesExist= [fileManager copyItemAtPath:filePath toPath:path error:&error];
NSMutableDictionary* plistDict=[[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
}
[plistDict setObject:#"si" forKey:#"stato"];
[plistDict writeToFile:path atomically: YES];
}
You can't write to the [NSBundle mainBundle] location. In order to write files like a plist, you should save in the documents folder, this way:
NSArray *arrayPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *filePathToSave = [arrayPaths objectAtIndex:0];
If the plist is part of your app, I would recommend you, in the first launch, to already copy it to the documents folder using the same filePathToSave, so you will always look at it there, both to read or to save.
This is a big mistake, as the main bundle only is readable and only composed at compile time in the App Bundle. The App Bundle lives in a separate place, whereas the data you should write to disk should be placed into the Documents, Temporary or Library folder of your sandbox.
To gain more understanding please read the official File System Programming Guide.
Everything you need to know is written there.
You can also write to subfolders and you should choose between the 3 above mentioned main directories in terms of backing up, when syncing with iTunes or iCloud. For instance contents in the tmp Folder won't be backed up.
You can not write to the mainBundle on an iOS device. You will have to save the file to a directory and modify it there.
Just to bring the answers into the modern world - you should really be using the URL based methods for getting directories:
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *URLForDocumentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]

Resources