i maintain a sqlite db in my iPhone app so, i was wondering how u could persist a single version and updated one.
its said that if want to make updates to the db u should copy it to the documents folder
on my iPhone simulator the documents folder changes like everytime i open the app
if its copied and changes was made .. how come the app reflect the updates to the db in my app bundle
so when the app is up again the app copies the db again from my bundle to the documents folder to be able to make changes to it..
my problem is that sometimes i make changes and load the app again to find that the changes are gone!
all what i have done:
created the db using terminal on a certain path and on resources folder in xcode right clicked add existing file
i do this each time i load the app :
-(void)DBinit
{
// Setup some globals
databaseName = #"articlesdb.sql";
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [[documentsDir stringByAppendingPathComponent:databaseName] copy];
NSLog(databasePath);
// Execute the "checkAndCreateDatabase" function
[self checkAndCreateDatabase];
}
-(void)checkAndCreateDatabase
{
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databasePath];
// If the database already exists then return without doing anything
if(success)
{
[fileManager removeItemAtPath:databasePath error:nil];
return;
}
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];
fileManager release];
}
Xcode, in conjunction with the iPhone Simulator, creates a new app folder for each build of your app and will move the the old documents folder and its contents over to the new app folder. It is up to you to check the contents of the documents folder and decide whether a file you have in your Bundle is preferable for use over the one that is in the documents folder. You should make this decision wisely and not lightly.
This is the right behavior. The user of your software may have made some significant changes and modifications to the database. They probably would not like it if, when upgrading for a minor bug fix or even for feature enhancements, their database changes, their high scores, their modified images, or whatever your app saves, suddenly gets replaced with a generic version.
What you should do is to compare the data in your new database, vs the data the user has, and in the case of SQLite, your should import or copy the user's entry into your new database, and then you can move your new database over to the documents folder, removing the user's but replacing it with one that has the user's data and your new data.
In short: make sure you don't replace an existing database with a new one from your bundle, since that will blow away the user's changes.
Related
I have an app which displays text data with images and or videos which is displayed in a UITableView. I have a requirement now that the data must be available offline. I am thinking of pruge and store data every time I have a connection i.e new data is being downloaded. How can this be achieved - storing the data in a mysql table or any other solution. Please note that I am using AFNetworking to stream the images in uiimageview. The images and videos should be stored as blob is the local mysql database? Please note that my project is not using coredata.
The best way would probably be to download and store the media files in the caches directory.
Then, when you need to access a file, you can check to see if it exists, and if so, use it, and if not, handle that condition in whatever manner you need.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
if(![fileManager fileExistsAtPath:[cachePath stringByAppendingPathComponent:#"myfile.zip"]]) {
//create it, copy it from app bundle, download it etc.
}
//start using it
The caches directory is not backed up with the rest of your app, so if you have large files you will not impact the user's backup storage sizes this way.
If you absolutely must have the files in the backup, use the Documents directory instead of the caches directory.
You said you were using AFNetworking, which can stream a download to a file just fine, and even has support for turning the task into a background operation so that it can finish after the app has been closed.
I've got an iOS app that imported files from an email attachment.
I've noticed that once i'm finished with it it places the imported file into Documents/Inbox.
Should my app be deleting these files or does the OS eventually get around to clearing them out?
if so, how? i've tried:
[[NSFileManager defaultManager] removeItemAtPath:[self.url path] error:nil];
However it doesn't seem to reference the file in the inbox, even though self.url is the correct path to my import file.
System does not clear imported files, so you should clear them manually when it is necessary, but not to delete the Documents directory.
How to clear the NSDocumentsDirectory you can find here
If you want to delete files from the inbox use the same code adding
...
NSString *path = [NSString stringWithFormat:#"%#/Inbox", documentsDirectory ];
NSArray *directoryContents = [fileMgr contentsOfDirectoryAtPath:error:&error];
...
Read the reference
From apple doc:
Use this directory to access files that your app was asked to open by
outside entities. Specifically, the Mail program places email
attachments associated with your app in this directory; document
interaction controllers may also place files in it.
Your app can read and delete files in this directory but cannot create new files or write to existing files. If the user tries to edit
a file in this directory, your app must silently move it out of the
directory before making any changes.
The contents of this directory are backed up by iTunes.
To get to my application documents folder, I use this code:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
I want to access this folder, however:
~/Library/Mobile Documents
How can i easily access this as a path value? Can I do this in a similar way?
The benefit of using the constants to access system provided directories is that if Apple decide to change the structure, your application will still work. Hardcoding in something like ~/Library/Mobile Documents is brittle.
However, you can access the Library directory with the same NSSearchPathForDirectoriesInDomain with the NSLibraryDirectory constant. Then, you should just append the Mobile Documents directory path.
// Set the NO to YES to get the full path, not the ~ version.
NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, NO) lastObject];
path = [path stringByAppendingPathComponent:#"Mobile Documents"];
Looking at the constant values in http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/Reference/reference.html#//apple_ref/doc/c_ref/NSSearchPathDirectory, it appears there is no specific constant for the Mobile Documents directory, so the hardcoding approach might be your only option.
Mobile Documents are iCloud documents. So you want to store documents in iCloud.
On OS X they are definitely in ~/Library/Mobile Documents (10.7 and 10.8), but on iOS you should not look.
"All documents of an application are stored either in the local sandbox or in an iCloud container directory."...
"A user should not be able to select individual documents for storage in iCloud. "
http://developer.apple.com/library/ios/#documentation/DataManagement/Conceptual/DocumentBasedAppPGiOS/ManageDocumentLifeCycle/ManageDocumentLifeCycle.html
So if your user picks iCloud then you should use iCloud.
How long the iCloud document model will last is anyones guess, but that's the way it works today. The whole thing seems a masterpiece of poor UI design, as this direct answer to your question shows:
-(NSURL*)ubiquitousContainerURL {
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}
In my recent macOS app I had the same need: how to gain access to the root folder of your iCloud directory.
IMPORTANT!
This is written for an unsandboxed version, since the app is only intended for myself. If you plan to release an app on the Mac App Store, do not turn off sandboxed version.
I turn off sandbox in the app's entitlements file.
This code will access your iCloud root folder:
let pathToiCloudFolder = NSString(string: "com~apple~CloudDocs").expandingTildeInPath
let backUpFolderUrl = FileManager.default.urls(for: .libraryDirectory, in:.userDomainMask).first!
let backupUrl = backUpFolderUrl.appendingPathComponent("Mobile Documents/" + pathToiCloudFolder)
print("Backup Folder:", backupUrl)
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 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.