So as a learning exercise, I am trying to make a simple file browser that interfaces with a file storage mechanism. (Think dropbox or box.net) I want to add a feature that would allow the user to flag a file for local storage so they could view it when they were not connected to the network. Is there an apple API that allows for something like that.
Perhaps there is a way to add the documents to the local bundle and then access the files that way at a later time? I haven't been able to find much documentation on this. Any insight, guidance or just general advice would be greatly appreciated. Thanks!
You can find the application's document directory thus:
/**
Returns the path to the application's Documents directory.
*/
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
Then get the URL (i.e. pathname) of a file within that directory:
NSURL *fileURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent:theFilename]];
and use Cocoa file-writing methods (e.g. of NSString, NSData), or you can just use ordinary stdio and so on.
Related
My app just got rejected by Apple with the following reason:
2.23
We found that your app does not follow the iOS Data Storage
Guidelines, which is required per the App Store Review Guidelines.
In particular, we found that on launch and/or content download, your
app stores 10.3 MB. To check how much data your app is storing:
Install and launch your app
Go to Settings > iCloud > Storage & Backup > Manage Storage
If necessary, tap "Show all apps"
Check your app's storage
The iOS Data Storage Guidelines indicate that only content that the
user creates using your app, e.g., documents, new files, edits, etc.,
may be stored in the /Documents directory - and backed up by iCloud.
What I do is, I deliver the database in the resource folder with about 10 MB and copy that database to the library path on initial startup (see code below). When looking at my app settings after the startup within the device settings, it actually says, that the documents & data folder contain this 10 MB of data. The app does not need that database anymore when it is once installed, so I just tried to remove the DB from the resource folder, when the copy is done by using the removeItemAtPath. But there seems to be a permission issue with that.
Here the code I am using to populate the database at initial startup:
// Copy the database from the app resource, if it is not already existing in the library path
- (void) copyDatabaseIfNeeded {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *dbPath = [documentsDir stringByAppendingPathComponent:#"abiliator.sqlite3"];
BOOL success = [fileManager fileExistsAtPath:dbPath];
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"abiliator.sqlite3"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success) {
NSLog(#"Failed to create writable database file with message '%#'.", error);
}
else {
success = [fileManager removeItemAtPath:defaultDBPath error:&error];
if (!success) {
NSLog(#"Failed to remove the source database file with message '%#'.", error);
}
}
}
}
After researching for hours about that issue, I am actually pretty sure, that I am not doing ANYTHING wrong at all. The Library path is the location to store updateable files according to Apple documentation. Especially if the data is wanted to be backed up and hidden / not exposed to the user. Both is the case for my app, I want the data to be backed up and I don't want the user to see my database. So Library seems perfectly right. Only thing I could think of is the size of the database in the resource directory. I could reduce that by zipping it. But what are the limits? Neither the reviewers nor the documentation could tell me anything specific on that.
So what is actually wrong? And if the resource directory is not the right place to store my source database for initial setup, what other directory could I use in my project?
thanks a lot for any hint.
René
Got a reply from Apple mentioning that I am supposed to store the user data in a different database than the data I am delivering. Though I am not very keen on that, as it increases the code complexity unneccessary and the user data in my app can get larger than the one I deliver anyway, I would like to make sure, that I get the correct solution approach for that now.
What I intend to implement is 2 directories: /Library/UserDB and /Library/AppDB. The AppDB would contain the delivered DB and the UserDB would contain the user data only and I would flag the AppDB Dir as non-backup. Guess that would make Apple happy and get my app approved for that matter, am I right? Would appreciate any opinion about that approach before I start implementing.
I am unknown of file manager in iphone
Can anyone explain the concept of file manager here
thank you
Quoting from the Apple Low-Level File Management Programming Guide
You use an NSFileManager object to
perform many generic file-system
operations—for example you can:
Create directories and files.
Extract
the contents of files (as NSData
objects)
Change your current working location in the file system.
Copy, move, and link files and directories.
Remove files, links, and directories.
...
An important point to understand is that your application runs in a "sandbox" - it has access only to its own files, not files created by other applications.
The section on the file system in the iOS Application Programming Guide describes the layout of your application and any files it creates.
For example, here's how you might use NSFileManager to open a file named "Defaults.plist" in your application bundle and read it into an NSData object:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Defaults" ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
How can I download a PDF document from the web to my apps Document folder?
I want to grab a version of a PDF from the web as an application is launched and use this in the app. It will need to be copied to the Apps Documents folder so it can be used from there until the app is launched again. Obviously I will need to check the date stamp on the doc to see if we need to update it. Maybe there's a way to check the PDF metadata for a version number?
Quick and dirty -- no error checking and no nice callbacks that you would receive from using NSURLConnection and its delegate protocol (which may be nice if you want to be notified of errors, progress, etc.)
// fileURL is an NSURL that points to your pdf file
NSData *fileData = [NSData dataWithContentsOfURL:fileURL];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *localFilename = [(NSString *)[paths objectAtIndex:0] stringByAppendingPathComponent:#"myPDF"];
[fileData writeToFile:localFilename atomically:YES];
Look at the documentation for NSURLConnection
An NSURLConnection will allow you to make an HTTP request to a webserver and download the data.
You should implement the NSURLConnectionDelegate methods. One of the important ones is connection:didReceiveData: - here you should append the received data to an initialised NSMutableData object.
Eventually, after the download completes, you should get the connectionDidFinishLoading: callback, and from there you can save the downloaded data to the disk.
You can use the NSSearchPathForDirectoriesInDomains() function to get the path to the documents folder by passing the NSDocumentDirectory constant.
Finally, to save the data, you can use the writeToFile:atomically: method on NSData.
I have noticed that in the CoreDataBooks example a default database is copied to the documents directory if the file doesn't already exist there:
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"CoreDataBooks" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
My question is, does this double the space it takes up?
i.e. There are now 2 databases, one in the bundle one in the documents folder.
I have a much larger database and a whole bunch of images totaling to about 50mb. Is there another way to go about this without copying the data?
In the example this is done so there is a default file to write to. If this is not done, one would have to create the file in code. The reason for this is because the app bundle is in a sandbox where it is forbidden to write to.
If you need the database to be editable, you must move it outside the app bundle.
AFAIK, Apple strongly recommends against modifying files in the bundle. So if the data are read-only (like, most likely, your images), it's OK to keep it in the bundle. A mutable database is a whole another matter.
I have an iPhone application in which a number of domain objects are populated with user-entered data. In order to restore state after being interrupted, these objects implement the NSCoding protocol and are written to disk (the Documents directory) in the applicationWillTerminate message. Then, when the application is launched again, the bytes of data are loaded up and the domain objects are repopulated. Code to get the documents directory is as follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
This worked great in the simulator but once I deployed the app to the iPhone it stopped working. The reason why is iPhone error code 513 - which apparently means "permission denied." There is a note in the iPhone dev docs that explain a little bit more -
On the device, the returned path
(documentsDirectory) is similar to the
following:
/var/mobile/Applications/30B51836-D2DD-43AA-BCB4-9D4DADFED6A2/Documents
However, on the Simulator, the
returned path takes the following
form:
/Volumes/Stuff/Users/johnDoe/Library/Application
Support/iPhone
Simulator/User/Applications/118086A0-FAAF-4CD4-9A0F-CD5E8D287270/Documents
This is the exact behavior that I'm seeing. I'm not really sure how this relates to getting a permission denied error and what I can do to fix it. It does say below -
To read and write user preferences,
use the NSUserDefaults class or the
CFPreferences API. These interfaces
eliminate the need for you to
construct a path to the
Library/Preferences/ directory and
read and write preference files
directly. For more information on
using these interfaces, see “Adding
the Settings Bundle.”
If your application contains sound,
image, or other resources in the
application bundle, you should use the
NSBundle class or CFBundle opaque type
to load those resources. Bundles have
an inherent knowledge of where
resources live inside the application.
In addition, bundles are aware of the
user’s language preferences and are
able to choose localized resources
over default resources automatically.
For more information on bundles, see
“The Application Bundle.”
I don't see how I can use the Application Bundle to load bytes of data though. Any help or examples?
Not sure how this works, but apparently using stringByAppendingPathComponent instead of stringByAppendingString for path creation fixed the problem.
The paragraph related to the application bundle refers to:
NSString *path = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"png"];
This and other methods from NSBundle allow you to refer resources from inside the application bundle without actually knowing where the application bundle is located.
Disclaimer: I haven't worked with the iPhone.
With the disclaimer in mind, in plain OS X it's considered bad form to write stuff inside the application bundle. You save stuff under the user's Application Support directory -> see NSApplicationSupportDirectory.
I got this error (513) because the path was wrong. Double checking my path fixed the problem :)