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.
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 am downloading some mp3 files through my application using NSURLConnection. Actually where can I save the downloaded file. Someone says that saving in to NSDocumentDirectory will lead to app rejection.
Can I save the file to NSCacheDictionary and retrieve this from itunes?
I used this bit of code to save files to NSCacheDictionary
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(
NSCachesDirectory, NSUserDomainMask, YES)
objectAtIndex: 0];
NSString *documentsDirectoryPath = [cachesPath stringByAppendingPathComponent:#"music.mp3"];
[receivedData writeToFile:documentsDirectoryPath atomically:YES];
Can I use like this?
If you save the files to NSCacheDictionary you will not be able to retrieve them from itunes.
Edit:
You can store the mp3 files to NSDocumentDirectory and set "do not backup" flag
for setting the flag you can check the Technical Q&A QA1719.
For additional information you can check the docs.
specifically:
Use this attribute with data that can be recreated but needs to
persist even in low storage situations for proper functioning of your
app or because customers expect it to be available during offline use.
This attribute works on marked files regardless of what directory they
are in, including the Documents directory.
EDIT: So far, the best I've been able to come up with is a pop-up to ask the user to disable iCloud sync, along with moving all the data to the Documents directory so it won't get wiped: In iOS5, is it possible to detect if a user has an app set to back up?
I develop offline mapping application for iPhone/iPad.
We used to store all of the data (many gigs potentially) in the Caches directory.
As of iOS5, the files in the Caches directory can be randomly deleted when the user's hard drive starts getting full.
How can I store local data, without the data being synced to iCloud, iTunes, and without it being randomly deleted? My local data is a large directory tree with many small data files, in thousands of subdirectories.
I moved our directory tree from the library cache directory to a data.nosync directory in the documents directory, because we had read this might be a solution. However, the data in the nosync folder is still being backed up to iCloud.
Here is now I create the directory:
NSString* noSyncDirectory() {
static NSString *directory = nil;
if (!directory) {
directory = [[NSString stringWithFormat:#"%#/%#",
documentsDirectory(), #"data.nosync"] retain];
[Constants createDirectoryIfNeeded:directory];
}
return directory;
}
From: https://developer.apple.com/library/ios/#qa/qa1719/_index.html
You can use the following method to set the "do not back up" extended attribute. Whenever you create a file or folder that should not be backed up, write the data to the file and then call this method, passing in a URL to the file.
#include <sys/xattr.h>
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
}
More information can be found: https://developer.apple.com/icloud/documentation/data-storage/
Further note: While the developer documentation incorrectly implies ("These files will not be purged and will not be included in the user's iCloud or iTunes backup.") that the do-not-backup flag doubles as a do-not-purge flag that is not the case. Simply leaving files in the Caches Directory and flagging them do-not-backup will not prevent their wipe.
Perhaps you can disable backup for your app and store data files somewhere else in the app tree. Any stuff that needs to be backed can be put in a common area outside your app.
You might be able to do it in provisioning: invalid code signing app submission
or settings:
When you want to give the user the option to enable or disable iCloud usage entirely for your app. If your app includes a Settings bundle or inline preferences, you could include a preference to toggle whether your app stores content in iCloud at all. For example, an app whose data consists entirely of privately managed files might do this to give the user the choice of how those files are stored.
or by removing the com.apple.developer.ubiquity-container-identifiers entitlement (which could get auto-added) with Xcode: Configuring Your App's iCloud Entitlements
Otherwise you might need to issue a warning with instructions on disabling through the UI:
http://www.pcmag.com/article2/0,2817,2394702,00.asp#fbid=bpIwPLZ1HeQ
Another workaround is to group the maps into collections that are installed as separate applications. That would be a way to store the data without creating any directories that sync or get backed-up. The data will be stored in the the .app directory and will be protected.
Depending on how the cache space reclamation function works, it might not delete recently accessed or modified files. You could try periodically touching them on a timer. You could also add some old files as decoys and detect when they've been deleted or when space is low to at least issue a warning or re-download the deleted objects...
This issue might not have a workaround yet... You could possibly try calling URLForUbiquityContainerIdentifier explicitly, since it does some initialization on the first invocation. Then create a sub-directory with a .nosync suffix (based on this example).
The first time you call this method for a given container directory, iOS extends your application sandbox to include that container directory. Thus, it is important that you call this method at least once before trying to search for files in iCloud. And if your application accesses multiple container directories, you should call the method once for each directory.
The doc on .nosync:
To ensure that the persistent store itself is not synced by iCloud: when you set a value for the NSPersistentStoreUbiquitousContentNameKey, UIManagedDocument puts the persistent store in a .nosync directory inside the document package. If you make use of additional content (using the writeAdditionalContent:toURL:originalContentsURL:error: method), you must make sure that the document directory is not a package. Typically you give the document directory an extension that is not recognized as a document extension.
You may want to ensure you have the com.apple.developer.ubiquity-container-identifiers entitlement.
The iCloud Containers field identifies the list of container directories that your app can access in the user’s iCloud storage. (This field corresponds to the com.apple.developer.ubiquity-container-identifiers entitlement.)
Maybe uncheck "Enable Entitlements" in the summary pane of the project or edit the profile to remove the *ubiquity settings. (Notes for ios 5 beta 7 reference entitlements.)
There is also the setUbiquitous function:
setUbiquitous:itemAtURL:destinationURL:error:
Sets whether the item at the specified URL should be stored in the cloud.
Parameters
flag
Specify YES to move the item to iCloud or NO to remove it from iCloud (if it is there currently).
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/Reference/Reference.html#//apple_ref/occ/instm/NSFileManager/setUbiquitous:itemAtURL:destinationURL:error:
There is a newer way to prevent iCloud syncing of data without using extended attributes directly:
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
{
NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
See Apple's Technical Q&A QA1719 for more details.
Did you try just naming the directory ".nosync" without the data in front? It could be generally . directories are ignored, or perhaps that specifically.
But, it seems like the behavior is just as a user would want it - potentially gigs of space used by an application they may not be using currently, where the space could be reclaimed automatically. I am not sure but you would think the system would be smart about reclaiming files created recently only after there was no choice, so if the user had just stored maps recently they would not be deleted unless there was no other choice.
For the purposes of filing a bug I am going to ask for a way to mark a directory for user prompting before deletion - so that if they are syncing a lot of movies and it would clear out a data set like the maps you are talking about, the user would be asked if they want to remove the data "offline maps" from application "MyCoolMapper" to proceed with the sync.
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 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.