NSDocumentDirectory or NSBundle resourcePath with Sqlite? - objective-c

i was wondering why we search a path with NSDocumentDirectory at first here :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"Sports.sqlite"];
and later on we compare this path with another path, using this time resourcePath from the NSBundle :
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"Sports.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath
toPath:writableDBPath error:&error];
What is the difference between both?

In this situation you are (normally one time only, unless you need to restore database) copying the database from your read only bundle into your documents directory so that a user can read/write to it. This is useful if you want to pre-seed a database or just have the structure set up.
Your documents directory is read/write and your bundle is not therefore you need to have the sqlite in your documents directory for it to be used properly.
The first part of code is simply getting you the path for where you want the sqlite file to live in your documents directory. Which ends up being held in writableDBPath.
Next you get the path from your bundle (defaultDBPath) and use the two paths to
... copyItemAtPath:defaultDBPath toPath:writableDBPath ...
This gives you a read/write database that you provide in your bundle. Why would you do this instead of running your SQL on the device to create the schema? This allows you to pre-seed the database with some data. It can sometimes be easier to use a graphical tool to set up and edit your sqlite file

What you mean by "comparing" the two paths?
What I see is a file copy from your app resources directory to the user document directory.
So what is happening is that a default version of Sports.sqlite that is to be bundled with the app (and thus is available in the resources directory) is copied to the user directory where the user can modify it.
The first chunk of code simply build the destination path (a string); the second chunk build the source path (a string) and then makes the copy.
Does it make sense?

Related

.plist file writing failed! what is wrong with this code?

Nothing is written in my plist file after this code. What is wrong with my code?
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"aFile.plist"];
NSMutableDictionary *reqData = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
/*
some modifications to "reqData"
*/
[reqData writeToFile:finalPath atomically:YES];
Nothing is written in file. what could be the problem?
You'd better write to Document folder:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [paths objectAtIndex:0];
Update
According to NSDictionary Class Reference:
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.
Is there any objects with a type other than these valid ones in your whole dictionary?
You are trying to write the file back to the app bundle.
That's what's most likely causing the error.
What if you try writing it somewhere else (e.g. in your desktop folder)?
If you're including the plist with your application, you'll want to copy that file into the Documents directly when the app first starts up (if it hasn't already been copied there). Then, any read and write operations you want to do on the plist should be done from the copy in the Documents directory instead of the version in the app bundle.
First Thing to remember:
You can read a plist file from resources but you can't modify it.
if you want to modify,
copy that file to Documents directory
copy contents of plist into array or dictionary depending on its type
make changes you want
Finally.... save it back to documents directory
that do the trick

Writing an NSString to a Relative File

I'm getting strange behavior writing NSString and NSData objects to relative file paths. Here's an example:
NSString *string = #"I am a file!";
NSError *error = nil;
NSString *fileName = #"text.txt";
BOOL written = [string writeToFile:fileName atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (written) {
NSLog(#"Successfully written to file.");
} else {
NSLog(#"Error: %#", [error localizedDescription]);
}
When I run this I always get "Successfully written to file.", but the file is never there. Somehow the program thinks it was successful and no error is generated.
What am I doing wrong? (I'm on Mac OS X Lion)
This writes to the current directory. The default current directory when you run something under Xcode 4 is going to be ~/Library/Developer/Xcode/DerivedData/<prodDir>/Build/Products/<configuration>. You can override this using a Scheme. When you run a program from the commandline, then the current directory is whatever the current directory was when you ran the program. If you use Finder to launch the app, then the current directory will often be /.
In principle, it's fine to write the current working directory. It's very common to do this in command-line apps. So regarding #craig's comment about writeToFile: expecting an absolute path, I don't think that's really true. It expects and writes to a path. It doesn't care if it's absolute or relative. This is a Foundation class, and is just as useful in a command-line program as a GUI.
But in a GUI app, you should avoid relative paths. In principle, you could set the current directory and then write the file, but this is usually a bad idea in a large program since it's not thread safe (there is only one cwd for the whole program). And GUI apps tend to have somewhat unpredictable current directories, so it doesn't make for a good user experience.
But to the question of why you didn't get an error, it's because it probably successfully wrote it. You just didn't know where to look.
NSFileManager * fm = [NSFileManager new];
NSString * dirPath = [fm currentDirectoryPath];
NSString * absPath = [dirPath stringByAppendingPathComponent:#"myfile.file"];
[fm release];
keep in mind that currentDirectoryPath reflects your programs working directory until you change it with -changeCurrentDirectoryPath:, the programs working directory can be different depending on how it was launched, and can't be relied upon.
The first parameter to the writeToFile: method (in your example) is a relative path, but you probably want to use an absolute path. Otherwise, the system will place your files relative to the current executable. When you're running inside Xcode, this might not be where you expect them to end up. (As Rob mentioned in his answer, this location is somewhat buried, and can change depending on which version of Xcode you're using.)
If you want to build up a directory path using NSString objects, I would recommend the stringByAppendingPathComponent: method:
...
NSString *directory = #"/Users/Mikael/Desktop";
NSString *filename = #"MyFile.txt";
NSString *fullPath = [directory stringByAppendingPathComponent:filename];
...
Note that this method will take care of making sure your slashes are well-formed.

Incorporate a text file into the build of my app

I want to incorporate a text file into the "Build" of my program for the iPhone. Other then copying and pasting into the code, how can I do this? I have many data files that I would like to include in the build.
You can use the NSString method stringWithContentsOfFile:encoding:error: to read the contents of any file in your project into an NSString.
Include the file in your project (no copy and paste required). Simply right click on the list of files, select Add, select Existing files, and navigate to the file to include.
Then, in the code where you want to load the file, use
NSString *txtFilePath = [[NSBundle mainBundle] pathForResource:#"myTxtFile" ofType:#"txt"];
NSString *txtFileContents = [NSString stringWithContentsOfFile:txtFilePath encoding:NSASCIIStringEncoding error:&error];
However, if you want to have more structure, then you should use a plist to store an NSDictionary.
You can add a .txt file to your application bundle. Please explain more what actually you want to do?
You can use plists to provide prepared data lists in your app. You can read them in with the NSArray function arrayWithContentsOfFile: or the NSDictionary function dictionaryWithContentsOfFile:.

How do you get the directory parameter of pathsForResourcesOfType:inDirectory:?

In my Resources group in XCode I have a subgroup called "AUDIO", and a subgroup of that called "words_audio". I have a bunch of sound files I'm trying to get the paths of. I tried
NSArray *pathsForWordAudio = [NSBundle pathsForResourcesOfType:#"mp3" inDirectory:#"words_audio"];
as well as a few other string parameters, but none of them work. The hierarchy setup of the folders in the directory is the same as the setup of the group in XCode.
Basically, when you create a group in XCode - no corresponding folder is created. Moreover, result app bundle for iOS contains no folders at all (except system-dependent like "_CodeSignature" and localizations - "*.lproj"). So pass nil as directory parameter.
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
Code lifted from this SO thread.

Get image paths from NSBundle in Objective C?

I have about 60 images I want to store in Core Data, 30 of which are avatars and have the prefix of avt_filename_00X.png and 30 of them are smaller and have a different prefix.
Rather than storing all the images as BLOBs in Core Data/SQLite, I want to store the paths for each image found (in the same way you would store image paths for a MySQL database).
However I am not sure how to grab the path of the image as found in NSBundle.
I can get the path to the NSDocumentDirectory via:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager fileExistsAtPath:documentsDirectory];
NSLog(#"documentsDirectory = %#", documentsDirectory);
And I can load the image and add it to an array.
if (qty == 0)
{
//NSLog(#"fileToLoad = %#", fileToLoad);
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileToLoad ofType:fileExt]];
[self.avtList addObject:img];
[img release];
} else {
// load multiple image into an array
// not coded yet
}
But, what I'm unsure of is:
How do I grab the path where the computer found the image once its inside the NSBundle?
How can I be sure that the path will work when the app is on a device?
The idea would be to get all the images stored in an array and then push them to Core Data/SQLite at a later time.
The correct way to get the full path to a resource in the main bundle is:
[[NSBundle mainBundle] pathForResource:#"avt_filename_00X" ofType:#"png"]
(or you can supply the empty string for 'ofType' if you prefer to include the extension in the resource name)
But nowhere in the docs is the path guaranteed to remain the same across devices, operating system iterations, etc. It's the path to that file from the application bundle in the current environment, guaranteed to remain valid for the duration of this run of the application only.
Because the path to the application, and hence to its resources, isn't guaranteed to stay the same, I think it's explicitly unsafe to put it in an SQL database by any means.
Could you perhaps adopt a scheme whereby a filename starting in / is a complete path, one without a / at the start is assumed to be in the bundle, meaning that you can apply the logic on the outside of the database?
How can I be sure that the path will
work when the app is on a device?
Therein lies the rub: you can't. You would be best to let the paths be handled on-the-fly, and perhaps just store the file names instead.