Writing values to a .plist more than once? - iphone

I have an iPhone App in the works, and I have a settings page within the app. I use a .plist to store the settings that the user picks, and then I read the data from the .plist later. This is not the -Info.plist that comes with it when you create the project, it is another plist file. When I tested the settings after coding it, it worked. The setting was able to be read, and it was the correct setting that I used. However, I went back to the settings in the same app-session, and changed the same setting. When I went to the app to see if it read the new setting, it only used the old (first) one. I tried again, but it yielded the same result. I am only able to write the setting once, and I cannot 'rewrite' or 'overwrite' the same setting. I cannot figure out what is wrong for the life of me.

You can't overwrite or write to files in the app bundle itself. If you want to write to a file, you should first copy it to the app's Library or Documents directory, something like this:
NSString *docsDir = [NSSearchPathsForDirectoriesInDomains(NSDocumentsDirectory, NSUserDomainMask) objectAtIndex:0];
NSString *plistFile = #"myplist.plist";
NSString *plistInBundle = [[NSBundle mainBundle] pathForResource:plistFile ofType:nil];
NSString *plistInDocsDir = [docsDir stringByAppendingPathComponent:plistFile];
[[NSFileManager defaultManager] copyItemAtPath:plistInBundle toPath:plistInDocsDir error:NULL];
// now `plistInDocsDir` is (over)writeable
However, for storing preferences of your app, it's better practice to use the NSUserDefaults class:
[[NSUserDefaults standardUserDefaults] setObject:#"Some string" forKey:#"MySettingKey"];
and/or create a Preference Bundle for your app.

Related

sqlite db path in bundle acces?

I want to punt my sqlite db in to a custom bundle and use that bundle in more than one project.
I have created a new target from Mac OS X FrameWork&Library -> Bundle, I have build the target and the I have put my sqlite db in the bundle.
I have added the bundle to my iOS project and try to read the db from it.
Now comes the problem: I can get the bundle and read its identifier but when I try to get the path for db I get null return string.
The code to read db path:
NSBundle *myDbBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle]pathForResource:#"myDbBundle" ofType:#"bundle"]];
NSLog(#"BundleIdentifier:%#",[myDbBundle bundleIdentifier]);
NSString *databasePath = [myDbBundle pathForResource:#"myDbName" ofType:#"sqlite"];
NSLog(#"DataBasePath:%#",databasePath);
Any sugestion, docs or tutorial about this issue are wellcome.
Update:
It seems that I can not load the bundle that is why when I try to read from it I fail.
Code:
NSError *error;
NSLog(#"LoadBundle:%#",[myDbBundle loadAndReturnError:&error]?#"yes":#"no");
NSLog(#"Error:%#",[error localizedDescription]);
So the real problme is how do I create a bundle in witch I place a sqlite db and witch can be read from ios app? To create the bundle I used the xcode template under Mac OS X so I think that I have to change something in the build settings, but I don't know what.
NSString *databasePath = [[NSBundle mainBundle] pathForResource:#"myDbName" ofType:#"sqlite"];
should be good enough to get the path to your myDbName.sqlite inside your main bundle
EDIT:
Okay, here is what I did, first, New File -> Resources -> Settings Bundle, create a new one, name it myDbBundle, then right click on that newly created bundle, add myDbName.sqlite in it, make sure that it's not inside en.lproj. Also, make sure when you create a new bundle, you tick the targets to include it into your current target, since this is not Settings.bundle, you want to include it. Then run you code, it works fine.
and here is the code I use
NSBundle* bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:#"myDbBundle" ofType:#"bundle"]];
NSLog(#"%#", bundle);
NSString* test = [bundle pathForResource:#"myDbName" ofType:#"sqlite"];
NSLog(#"%#", test);
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile:[bundle pathForResource:#"Root" ofType:#"plist"]];
NSLog(#"%#", dict);
the NSLog(#"%#", dict); is printing out correct value, which means it's successfully loaded
I haven't done this myself, but this blog seems to be what you are trying to do Universal framework iPhone iOS 2.0
It describes creating a framework that can be used to package up any files and then simply imported into other projects as needed.
Swift 3
let dbname = Bundle.main.path(forResource: "horoscope-release", ofType: "sqlite")!

Add String Attribute to CoreDataBooks

I've been messing around with CoreDataBooks and trying to add another field to the sqlite file in addition to the current three. I've added the attribute string in the .xcdatamodel, and declared it in the book.h, book.m, and localized file (all without changing the sqlite file). However, these changes never add the field in sqlite, and the app never loads. Each time I am deleting the app in the simulator and performing a build--> clean, but to no avail. I've also tried changing the sqlite file to match the .xcdatamodel but the app still fails to load.
Is this a problem with CoreDataBooks or me? Do I need to version the app before doing this? Doesn't seem like I should have to as long as I'm deleting the app in the simulator.
Anyone know how I can add this forth string attribute (sqlite field) in CoreDataBooks?
Thanks in advance!
I've found such a nice piece of code in CoreDataBooks
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"CoreDataBooks.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"CoreDataBooks" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
in CoreDataBooksAppDelegate.m:154, this means for me that every time apps does not find sqlite file it is copied from bundle where it was not changed by modification of CoreDataBooks.xcdatamodel.
Try to use another approach, or just modify bundled sqlite file.
If the app doesn't load then there might be something wrong with your datamodel. Check the console for error messages.
And you have to decide if you want to use sqlite or core-data. If you want to monitor sqlite files for changes or add fields to sqlite files you should use sqlite and not core-data.
If you want to use core-data you should ignore the sqlite file.
Figured it out. The application WAS creating the new sqlite file, I just needed to find it within the simulator files. Grrrr, so much time wasted when I was doing everything correctly. Lessons learned I guess :)

Data storing in plist works in simulaor but not in device

I am new to iPhone development. I have created plist such as from my previous post. It works well in simulator but not in device.
I am getting the saved value from the plist and checking for the condition. When I use simulator it works but not in device.
(1) You can't write to a file in the resource folder of an iPhone. It is part of the security system of the phone that prevent malicious code from altering an application after it has been installed.
(2) If you want to save new data to a file you need to write the file to one of the automatically generated folders. Whenever an iPhone app is installed on the device or simulator, the system creates a default set of folders.
(3) You want to write to either the Preferences folder or the Documents folder. If the data concerns the operation of the app, write it to preferences. If it contains user data write to Documents.
Preferences:
NSArray *sysPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES);
NSString *prefsDirectory = [[sysPaths objectAtIndex:0] stringByAppendingPathComponent:#"/Preferences"];
Documents:
NSArray *sysPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory ,NSUserDomainMask, YES);
NSString *docsDirectory = [sysPaths objectAtIndex:0];
Say you want to read a default preference plist file, make some changes and then save it the preferences folder.
NSString *plistPath=[[NSBundle mainBundle] pathForResource:#"PlistFileName" ofType:#"plist"];
NSMutableArray *defaultPrefs=[[NSMutableArray alloc] initWithContentsOfFile:plistPath];
//... modify defaultPrefs
NSString *outputFilePath=[prefsDirectory stringByAppendingPathComponent:#"alteredPrefs.plist"];
[defaultPrefs writeToFile:outputFilePath atomically:NO];

Problem getting path to .plist using [[NSBundle mainBundle] pathForResource

I'm an Objective C noob, and I don't know enough to explain the following problem.
This code works:
NSString *plistPath = #"/Users/andrewf/MyApp/Resources/Plates.plist";
dicPlates = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
My dictionary object is loaded with values as expected.
This code does not work:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Plates" ofType:#"plist"];
dicPlates = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
plistPath comes back with a value of nil. This is the case irrespective of whether I include inDirectory:#"Resources" in the call or not. All the examples that I have found do not include inDirectory when trying to open a .plist file in the Resources directory.
I have confirmed that the file exists in the correct location and even recreated it to be sure.
This seems like such a simple problem, but I am mystified. Please assist.
I think you're confused as to where pathForResource: is looking.
This works:
NSString *plistPath = #"/Users/andrewf/MyApp/Resources/Plates.plist";
Notice that this path does not point to your application. It points to your project directory. Your plist is supposed to be at #/Users/andrewf/MyApp/build/Release/MyApp.app/Resources/Plist.plist" (for an iPhone app this would be different, more like #"/Users/andrewf/Library/Application Support/iPhone Simulator/User/Applicatons/(unique id)/MyApp.app/Resources/Plates.plist"), and it is inside the Resources folder of your app that pathForResource: is looking.
What this implies is that your resource is not in the "Copy Bundle Resources" phase of your build target. You need to drag it from the Groups and Files area to inside that phase.
I have found the problem!
The .plist file was included in the target correctly.
When the .plist was originally created, I named the file "plates.plist". Immediately after creating the file, I renamed it to "Plates.plist", and this is what I used henceforth.
Even though the file was named "Plates.plist" in my resources folder, in the target section and in the iPhone Simulator location mentioned above, the file was named "plates.plist". Curiously, the contents of the file was still updated correctly.
Now that I have changed the code to refer to "plates.plist", and renamed the file in the Resources folder to the same for good measure, everything works. I can only assume that this is a bug in the iPhone SDK.
Check that the plates.plist file has been added to the target in Xcode.
If the file is not added to the target it will not be inside the build product and NSBundle will not find it.

Where to keep .plist file in iphone app

I am developing an iPhone app, in which i want use an .plist file to save some config variables.
in my xcode where to create that .plist file and how to access it???
thank you,
You would put in the resources folder. Then use something like this to load it:
NSString *file = [[NSBundle mainBundle] pathForResource:#"TwitterUsers" ofType:#"plist"];
NSArray *Props = [[NSArray alloc] initWithContentsOfFile:file];
Where TwitterUsers is the name of your plist file.
If your plist file contains keys and values you would make the second line an NSDictionary instead of NSArray.
To store the plist file in your Documents directory you will have to include plist file in your app and then on first launch copy it to Documents directory.
To get access to the file in Documents directory:
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
// <Application Home>/Documents/foo.plist
NSString *fooPath =
[documentsPath stringByAppendingPathComponent:#“foo.plist”];
I would recommend keeping it in the Resources directory in the app bundle, but you can just drag it into the project window. The NSBundle method pathForResource:ofType: should give you a path, which you can pass to NSDictionary's dictionaryWithContentsOfFile:.
Edit: Sorry, full code sample (thought I'd already copied & pasted):
NSString *path = [[NSBundle mainBundle] pathForResource:#"MyConfig" ofType:#"plist"];
NSDictionary *myConfig = [NSDictionary dictionaryWithContentsOfFile:path];
EDIT Just occured to me "config variables" might not be immutable. If that is the case, disregard this answer.
I would recommend you use NSUserDefaults to store configuration variables instead of rolling your own system.
If you are modifying a .plist inside your application bundle, you will risk invalidating the signature on the bundle, while will not end well.
If you must store and modify your own .plist, store it in the Documents directory, which is also where your application should store anything that it downloads, creates, etc. See File and Data Management and NSFileManager.
By default if you have included the plist in the project anywhere (under Resources or otherwise) XCode will copy it into the application bundle where you can get to it with the aforementioned pathForResource call. Just thought I'd mention that as you might prefer a grouping where you do not have it in resources...
you can find the example sqlite book ..... the silte data file was save into the directory 'Document' .... you will know the prcess
Why would it invalidate the bundle?
Would this plist file in the resources folder be backed up in iTunes like files in the documents folder?