Unable to write to plist file - ios

I've found several questions here on the site with people who have a similar problem, but I'm still not able to resolve mine. Here's the thing:
I'm making an iOS app that contains two plist files. When the user opens the app for the first time, the application takes care of moving these two files to the Documents directory. I know this code works, since I'm subsequently able to read from each of the files in the Documents directory path. However, I'm not able to write to any of them for apparent unknown reasons. Here is the code I use to write to the first plist file:
+ (void)insertNewElementWith:(NSArray *)objects andKeys:(NSArray *)keys {
NSString *filePath = [self returnPathForFirstPlistFile];
NSMutableDictionary *contentOfFirstPlistFile = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
[contentOfFirstPlistFile setObject:dictionary forKey:[NSNumber numberWithInt:[contentOfFirstPlistFile count]]];
[contentOfFirstPlistFile writeToFile:filePath atomically:YES];
[dictionary release];
}
returnPathForFirstPlistFile does work, since contentOfFirstPlistFile contains { } rather than null. Can anyone explain to me, though, why it keeps returning { }, even though the above method has been called several times.
Update
Here is the code I use to place the files in the Documents directory. placePlistsInDocumentsDirectory is only called when the user logs in through the app, so this only happens once.
+ (NSString *)returnPathForFirstPlistFile {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"First.plist"];
return filePath;
}
+ (NSString *)returnPathForSecondPlistFile {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSLog(#"%#", [paths objectAtIndex:0]);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"Second.plist"];
return filePath;
}
+ (void)placePlistsInDocumentsDirectory {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *firstPlistPath = [self returnPathForFirstPlistFile];
NSString *secondPlistPath = [self returnPathForSecondPlistFile];
if (![fileManager fileExistsAtPath:firstPlistPath]) {
NSError *error;
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"First" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath:firstPlistPath error:&error];
}
if (![fileManager fileExistsAtPath:secondPlistPath]) {
NSError *error;
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"Second" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath:secondPlistPath error:&error];
}
}

[NSNumber numberWithInt:[contentOfFirstPlistFile count]]
Gives you an NSNumber object. This is not a valid key for a dictionary, the key has to be an NSString. Use
[NSString stringWithFormat:#"%d",[contentOfFirstPlistFile count]]
Instead. Though, if you are using numbers as keys in a dictionary, why not use an array?
I have attempted to reproduce this issue myself and the only way I could manage it was to use a plist that did not have a dictionary as its root object. This returned a dictionary that was not nil, but did not allow any modifications. This could not then be written back to the file. Looking at some of your earlier questions, you were using an array based plist before, so this may be the issue.

Related

How to make a .plist file

I have an api inside are the city's ID, I do not want to request data every time,I want to writeToFile as plist,but the first written is too slow and memory skyrocketing.
Is there any method I can use to make it into a plist as local file,so users do not have to write again
Plist file obtained in this way is no data on the xcode view, but in fact it has already been written, you can find data through code
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *plistPath = [paths objectAtIndex:0];
NSMutableArray *marr = [NSMutableArray array];
NSString *filename=[plistPath stringByAppendingPathComponent:#"city.plist"];
NSLog(#"filename == %#",filename);
[marr addObject:#"字符串"];
[marr writeToFile:filename atomically:YES];
If you are about to create Plist without programmatically then follow these steps :
Right Click on Files and Select 'New File...' option.
Choose Resources from OS X tab.
An option for Property List is available.
Select an give an appropriate name.
This gets added to your project.
You can create a property list in Objective-C if all of the objects in the aggregate derive from the NSDictionary, NSArray, NSString, NSDate, NSData, or NSNumber class.
Use following code:
//Get the documents directory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"plist.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path]) {
path = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:#"plist.plist"] ];
}
NSMutableDictionary *data;
if ([fileManager fileExistsAtPath: path]) {
data = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
}
else {
// If the file doesn’t exist, create an empty dictionary
data = [[NSMutableDictionary alloc] init];
}
//To insert the data into the plist
[data setObject:#"iPhone 6 Plus" forKey:#"value"];
[data writeToFile:path atomically:YES];
//To retrieve the data from the plist
NSMutableDictionary *savedValue = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
NSString *value = [savedValue objectForKey:#"value"];
NSLog(#"%#",value);
For more details click here
Apple has also put a demo project for creating plist file here.
I wrote a class that caches API responses, so later when u request Countries endpoint, it will check defined endpoints to cache, if it find it in list and it exist in cache, it will return the cached one and will not complete performing the HTTP request, in docs i provided steps how to implement it.
MGCacheManager
Following function take serilizable object(dictionary, array) as input and convert it into plist and write it in document disrectory of application:
+ (BOOL) writeBundelPlist : (NSString *) plistName with : (id) newPlistData {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:plistName];
if (![fileManager fileExistsAtPath:plistPath]) {
NSString *bundle = [[NSBundle mainBundle] pathForResource:[plistName stringByDeletingPathExtension] ofType:[plistName pathExtension]];
NSLog(#"plistPath = %#", plistPath);
[fileManager copyItemAtPath:bundle toPath:plistPath error:&error];
}
return [newPlistData writeToFile:plistPath atomically:YES];
}
this method is very useful, thanks for evervone,thanks for rmaddy
do{
let data = try NSPropertyListSerialization.dataWithPropertyList(response.result.value as! NSMutableDictionary, format: NSPropertyListFormat.XMLFormat_v1_0, options: 0)
data.writeToFile(countryListPath, atomically: true)
}catch
{
}

Accessing Plist from user documents instead of NSBundle

Hello I have the following code which works perfect, it finds the Plist and assigns the NSDictionary to my NSArray called landingSites. The only problem is I want the user to be able to add sites so need to store the Plist in the user documents instead of the NSBundle:
plistPath = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
NSMutableDictionary *newDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
dictionaryKeys = [newDictionary allKeys];
self.landingSites = [dictionaryKeys sortedArrayUsingSelector:#selector(compare:)];
self.dictionaryFromPlist = newDictionary;
[Picker selectRow:0 inComponent:0 animated:NO];
for(NSString *parent in dictionaryKeys)
{
NSDictionary *parentData = [dictionaryFromPlist objectForKey:parent];
NSArray *child = [parentData objectForKey:#"Name"];
NSLog(#"%#", child);
}
So far I have produced the code below, but whenever I run the program, the if-statement ALWAYS executes as if the plist in never copied to the user documents. Also I do not know how to attach the NSDictionary contents to my landingSites array as I did above. Please help
(void) viewDidLoad {
[super viewDidLoad];
NSError *error;
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Data.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:path]) {
NSLog(#"copying database to users documents");
NSString *pathToSettingsBundle = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
[fileManager copyItemAtPath:pathToSettingsBundle toPath:path error:&error];
}
else{
NSLog(#"users database already configured");
}
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask,YES);
NSDocumentationDirectory is wrong. You are looking for NSDocumentDirectory or NSLibraryDirectory.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

Saving and reading objects from plist cause huge memory of 2G

I have a system to save lots of images to plist , that works good, except 2 main problems.
first, when it start the process ,memory on xcode goes to 2G ! (and down when done)
Second, it takes too long(10+ seconds for 100 images)compared to NSUserdefaults, which i told is slower than this .
I am archiving the data first.
What am i doing wrong for having so much memory and so slow saving ?
-(void)saveToFileWithData:(NSMutableDictionary*)dic
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"data.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path])
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath: path error:&error];
}
NSData *myData = [NSKeyedArchiver archivedDataWithRootObject:dic];
BOOL sucess=[myData writeToFile:path atomically:YES];
if(sucess)
NSLog(#"saved:%lu",(unsigned long)[myData length]);
else
NSLog(#"failed:%lu",[myData length]);
myData=nil;
}
The reading is like this :
-(NSMutableDictionary*)readFromFile
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //1
NSString *documentsDirectory = [paths objectAtIndex:0]; //2
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"data.plist"]; //3
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path]) //4
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"]; //5
[fileManager copyItemAtPath:bundle toPath: path error:&error]; //6
}
NSMutableDictionary *dic = [[ NSMutableDictionary alloc] init];
NSData *serialized = [NSData dataWithContentsOfFile:path];
//check first if file exist, than if it has content(empty file had 42 bytes-and crashes the archiver)
if ([[NSFileManager defaultManager] fileExistsAtPath:path] && [serialized length]>1000000)
dic = (NSMutableDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:serialized];
serialized=nil;
return dic;
}
Use them with :
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
dic = [self readFromFile];
//change dic
[self saveToFileWithData:dic];
There is no reason to copy the plist from the app bundle to the Documents directory just to read it.
A plist is not a good solution, in this case it is huge Put the images individual files and the image file names in the plist. Read the now small plist and then rest the images one by one. Reading each file individually uses a smaller amount of memory.
But, do you really want 500MB of images in memory all at once? After having read the plist of image information just read the images as needed, perhaps with a cache that purges images based on usage.

Why is my Plist loading from bundle rather than Document Library

I am pretty new to working with Plists and IOS. I am trying to load a tableview of records, and I am trying to load the Tableview from my documents folder in the project. It used to load from the bundle and then I pointed to the documents folder. I removed the plist from my project, and kept it in the documents folder, but I received a copy error: So, please look over my code and give me some idea of how to load from the documents folder in my application. I ran it in debug and it still loading only the 5 records from the plist in the bundle, not the 7 records that reside in my documents folder. Just to note, after I load the initial screen of tableView records, I take the option "+" at the top to load a blank screen to load another record, this I am writing to the document folder and it works, now I want to display those changes on the Master. I also am not certain how to refresh the screen. I have read a bit about "ViewDidLoad", but I am not certain how to use this method. Thank you in advance.
Regards,
NSLog(#"loading data");
self.navigationItem.title=#"Accounts";
// NSString *accountFile = [[NSBundle mainBundle] pathForResource:#"Accounts2" ofType:#"plist"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [paths objectAtIndex:0];
NSString *plistPath = [documentPath stringByAppendingPathComponent:#"Accounts2.plist"];
accounts = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
account = [accounts objectForKey:#"Account"];
number = [accounts objectForKey:#"Number"];
dueDay = [accounts objectForKey:#"DayDue"];
minAmount = [accounts objectForKey:#"MinAmount"];
balance = [accounts objectForKey:#"Balance"];
NSLog(#"data loaded");
First common things that without having plist in bundle how to copy them in to document directory.? so please keep your plist file it to Bundle then copy in to document directory like bellow code:-
BOOL success;
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"yourPlistName.plist"];
success = [fileManager fileExistsAtPath:filePath];
if (success) return;
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingFormat:#"yourPlistName.plist"];
success = [fileManager copyItemAtPath:path toPath:filePath error:&error];
if (!success) {
NSAssert1(0, #"Failed to copy Plist. Error %#", [error localizedDescription]);
}
and you can get this plist file using this bellow line of code:-
NSString *path = filePath;
NSMutableDictionary *plistdictionary = [[NSMutableDictionary alloc]initWithContentsOfFile:path];
you can directly get plist file from Resource (NSBundle) like bellow code:-
NSString* plistPath = [[NSBundle mainBundle] pathForResource:#"yourPlistName" ofType:#"plist"];
NSMutableDictionary *plistdictionary = [[NSMutableDictionary alloc]initWithContentsOfFile:plistPath];

getting data from plist to NSMutableArray and writing the NSMutableArray to same plist iPhone

Using this code I am trying to get the data from Templates.plist file but I am getting null value in array.
//from plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"Templates" ofType:#"plist"];
NSMutableArray *arry=[[NSMutableArray alloc]initWithContentsOfFile:path];
NSLog(#"arrayArray:::: %#", arry);
This is my plist file:
Also I want to know how can I add more strings to this plist file and delete a string from this plist.
First off you cannot write to anything in you mainBundle. For you to write to you plist you need to copy it to the documents directory. This is done like so:
- (void)createEditableCopyOfIfNeeded
{
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writablePath = [documentsDirectory stringByAppendingPathComponent:#"Template.plist"];
success = [fileManager fileExistsAtPath:writablePath];
if (success)
return;
// The writable file does not exist, so copy from the bundle to the appropriate location.
NSString *defaultPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Template.plist"];
success = [fileManager copyItemAtPath:defaultPath toPath:writablePath error:&error];
if (!success)
NSAssert1(0, #"Failed to create writable file with message '%#'.", [error localizedDescription]);
}
So calling this function will check if the file exists in the documents directory. If it doesn't it copies the file to the documents directory. If the file exists it just returns. Next you just need to access the file to be able to read and write to it. To access you just need the path to the documents directory and to add your file name as a path component.
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docDir stringByAppendingPathComponent:#"Template.plist"];
To get the data from the plist:
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
To write the file back to the documents directory.
[array writeToFile:filePath atomically: YES];
-(NSMutableArray *)start1
{
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:#"Templates.plist"];
if ([[NSFileManager defaultManager] fileExistsAtPath:plistPath]){
plistArr = [NSMutableArray arrayWithContentsOfFile: plistPath];
}
else {
plistArr = [[NSMutableArray alloc] init];
}
return plistArr;
}
- (void)createNewRecordWithName:(NSMutableDictionary *)dict
{
plistArr=[self start1];
[plistArr addObject: dict];
//[dict release];
[self writeProductsToFile:plistArr];
}
- (void)writeProductsToFile:(NSMutableArray *)array1 {
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:#"Template.plist"];
[array1 writeToFile:plistPath atomically:YES];
}
To get a plist into a mutable array, use this class method:
NSMutableArray *array = [NSMutableArray arrayWithContentsOfFile:path];
Then, add/delete strings from the array. To save the array back to a plist, use:
[array writeToFile:path atomically:YES];

Resources