iOS Documents Directory expand tilde in path method - objective-c

Just got a -1 for my answer on how to get the documents directory. I always do it like this:
NSString *documentsPath = [#"~/Documents" stringByExpandingTildeInPath];
And it always works as I expect it to. Are there any real underwater rocks with this method?

No, it does NOT always work. If your app is running in sandbox (which is required by the app store), you won't be able to get the real documents directory.
I just did some test and below is what I got:
/Users/<user-name>/Library/Containers/<app-name>/Data/Documents
But my real document foder is
/Users/<user-name>/Documents

Right method to call: NSSearchPathForDirectoriesInDomains()
As Apple documentation states:
Creates a list of path strings for the specified directories in the
specified domains. The list is in the order in which you should search
the directories. If expandTilde is YES, tildes are expanded as
described in stringByExpandingTildeInPath.
Example:
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)

Related

Does NSSearchPathForDirectoriesInDomains (NSSearchPathDirectory directory, NSSearchPathDomainMask ); ever return multiple values on iOS

Does the following method ever return multiple values when used on iOS, and if so, do you have an example of when this happens and how to know which element is the one you asked for?
NSArray* NSSearchPathForDirectoriesInDomains (NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde );
I am wondering because I am calling it with the parameters for getting a path to the Documents directory (see below), and assuming it returns an array with only one element. It is working just fine, but it occurred to me that I might need to make sure I will never get more than one element back. And if I do get more than one, I wondered how I would know which one is the one I asked for?
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = paths[0];
It's my understanding that on iOS you'll only ever get a single element in the returned array.
I believe it returns an array rather than a single value as it's used by OS X too and tries to be as general purpose as possible. On OS X it can potentially return multiple elements, representing the user's documents directory, the computer's documents directory, and so on, according to the domain mask.
As an aside, note that Apple recommends using the NSFileManager methods URLsForDirectory:inDomains: and URLForDirectory:inDomain:appropriateForURL:create:error: instead, stating that URLs are preferred over path strings.

How do I programmatically find the user's logging directory?

Given an app called ExampleApp, I want to find "~/Library/Logs/ExampleApp" basically without using hard coded paths. There exists NSSearchPathForDirectoriesInDomains, which you can use to find things like "~/Library/Application Support/ExampleApp" using the NSApplicationSupportDirectory search term, but there doesn't seem to be a search term for logging.
I don't think ~/Library/Logs is non-standard, since CrashReporter puts its logs there.
Try this :
NSString* libraryPath = [NSHomeDirectory stringByAppendingPathComponent:#"Library/Logs"];
Update (~/Library/Logs/AppName) :
NSString* bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleName"];
NSString* logsPath = [NSString stringWithFormat:#"Library/Logs/%#",bundleName];
NSString* libraryPath = [NSHomeDirectory stringByAppendingPathComponent:logsPath];
Cocoa doesn't provide a means for finding all of the standard directories. The old FSFindFolder() function can provide many more, but does involve converting from an FSRef back to a path or URL. Apple discourages its use, but it's still the only way to get certain standard directories without hard-coding. That said, it won't ever incorporate your app name. You have to append that.
Edited to add: the link to the legacy docs.

NSDocumentDirectory or NSBundle resourcePath with Sqlite?

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?

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.