Reputation: 45408
A critical bugfix update to one of my apps was recently rejected due to an alleged violation of the iCloud Data Storage Guidelines.
Here's how my app stores data (hasn't been a problem since the first version of my app was approved in 2009):
An update earlier this year was rejected for the same reason, but when I gave them the explanation above, the app status changed from "Rejected" to "In Review" to "Processing for App Store." They didn't send me any explanation, so I thought it was just a misunderstanding on the reviewer's part.
This time, the reviewer responded to my explanation by simply saying that non-user-generated data shouldn't be stored in iCloud and my update remains in the "Rejected" state.
But I don't understand what I'm supposed to do here. Because all of the user's work is kept in the database, it's not an option for me to exclude this from iCloud backup or store it in a Cache folder. Also, I can't really cleanly separate "user-generated" from "non-user-generated" data because the app works from the same database file. The initial, non-user-generated data will be quickly replaced with the user's own data, though the filename and directory location of the database will remain the same.
And even if there were no sample data in the database, any database-backed app will still have to generate an empty database when it starts - even if the only thing it holds is the app's database schema.
This must be a very common problem, but unfortunately it's not okay for me to just turn off backup - users put a lot of work into the data they store in my app, and iCloud backup is very important to them.
What options do I have at this point? Here's what I can see:
Contact Apple again and try to explain what's going on.
Can I set the file's backup attribute to NO and then only toggle it to YES when the user makes their first change? Is that okay technically and okay with Apple?
Remove my example data from the database. This will be really bad for usability and will increase my support load, but I'm willing to do it if it will get my update approved. However, I'll still have to create a stub database at startup to hold the empty database schema, so I'm not sure if that will even make a difference in the approval process.
Cry.
Anyone have any advice? I have to imagine that there are lots of other apps that use a database the same way mine does, but don't have the option of just disabling backup.
It's also very depressing that any changes I make will require another round of testing and app review, which will set back my critical update an additional 2 - 3 weeks. :/
UPDATE: There may be another option: can I simply save the file in Library/
instead of Documents/
, since their problem seems to be specifically with the use of the Documents folder? Will the file be backed up if it's stored in Library/
?
UPDATE 2: The thing I find most confusing is that any database-backed app (even if it uses Core Data, I assume) will have to create a database file containing at least the app's schema. Is the problem just that the size of my database is too big? Because I can't see how any database-backed application can avoid having to create a database at startup.
UPDATE 3: I'm using a custom SQLite interaction layer - not Core Data. Also, the example data consists of starter images, which the user will probably end up deleting as they start using the app.
Upvotes: 18
Views: 6355
Reputation: 4844
I have a really stupid idea. But maybe it is valid.
You can show a popup on startup asking "Would you like me to create some demo data". When the user clicks "Yes", its user generated data.
Upvotes: 4
Reputation: 5611
Since you are not using Core Data, but a custom access layer, I can only suggest not to place the "initial seed" database in the iCloud folder, but write a custom procedure that generates it at runtime (eg. when you first launch the app).
The best approach should be placing your real SQL file in the common application bundle, outside the iCloud shared folder. Through your procedure you will have to read all the contents of this file and recreate a clone in the shared folder, so that this new file will appear as user-generated content and not bundled content. This will require a bit of useless overhead, but I think it's required to avoid Apple from rejecting your app.
I suggest you not to backup all the images and leave them in the main bundle, if possible, since they are already present in every downloaded app, and then will uselessly occupy precious space on iCloud. Try to place in iCloud only what is strictly necessary.
First of all, placing a custom SQLite3 database file in iCloud is dangerous for a couple of reasons, the first being the fact that you will never be able to appropriately handle merge issues between different data if your app is being used on different devices concurrently.
I cannot give you a complete solution here, since it will require too much time and space. But I suggest you to look at the Session 227 talk from this year's Apple WWDC. They talk about using Core Data and iCloud together, also addressing the same issue you are facing right now (i.e. initial database seeding and synching through iCloud). If you access the WWDC page through your Apple Developer portal, you will be able to get a complete copy of the sample project.
Upvotes: 0
Reputation: 28339
As the ultimate solution, I suggest using CoreData, with your canned data as an additional read-only persistent store.
However, in the mean-time, you need a simple, quick solution. Create a simple screen that loads sample data into the user's database. Put a link in your "system" or "tool" screen so it can be done any time.
Specifically, though, bring it up when the app is first run. You are asking the user if they want sample data in their document, with the size of the data, so they are the one creating the document. Also mention that the sample data can be deleted or replaced at any time.
This should get you past the review.
Upvotes: 0
Reputation: 1337
CoreData and two persistent stores should be the way to go. But for you, when first copying the file set the com.apple.MobileBackup file attribute. Unset it after the user makes his first modification
Upvotes: 0
Reputation: 19
Just add the following attribute and Apple will let you pass (if you are not using
#include <sys/xattr.h>
...
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
int result = -1;
@try {
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
u_int8_t attrValue = 1;
result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
} @catch (NSException * e) {
DebugLog(@"Exception: %@", e);
}
return result == 0;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
...
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
[self addSkipBackupAttributeToItemAtURL:storeUrl];
...
Upvotes: 0
Reputation: 15376
3MB seems like quite a lot of sample data stored in a database. If you pull out the images and store references to the images in the database instead you should be able to get this usage down a lot. You can then change your database's image getter code to something like this:
- (UIImage *)image
{
NSString *imageName = self.imageName;
UIImage *image = [UIImage imageNamed:imageName];
if (!image)
{
NSString *imageLibraryPath = ...;
image = [UIImage imageWithPath:[imageLibraryPath stringByAddingPathComponent:imageName]];
}
return image;
}
- (void)setImage:(UIImage *)image
{
[self setImageData:UIImagePNGRepresentation(image)];
}
- (void)setImageData:(NSData *)imageData
{
NSString *imageLibraryPath = ...;
NSString *fileName = self.uniqueId; //or something else, UUID maybe?
NSString *filePath = [imageLibraryPath stringByAddingPathComponent:imageName];
[imageData writeToFile:filePath atomically:YES]; // maybe dispatch_async this into the background?
self.fileName = fileName;
}
The self.fileName
would be backed by your database.
By reducing your data storage in this way you should be able to get approved as you are not storing relatively large amounts of data on the phone. The images can also be in the app bundle this way and do not need to duplicated at all.
Upvotes: 5
Reputation: 126107
If you're using Core Data, you can keep the sample data in the app bundle. Open it as a read-only persistent store on the same coordinator as the read/write store in your docs directory, and Core Data will make sure things stay in the right place on save. This is explained in the "Best Practices for Using Core Data" video from WWDC 2012.
If you're using SQLite directly, it's not so easy but you can implement your own logic for doing same.
Upvotes: -1
Reputation: 8772
One work around would be to read the SQL file out of the bundle until the user tries to edit something. Then copy it over. Since the file is small it should not even require a spinner.
I had this exact problem with an App recently. In our case we we using core data and copied the SQL file on startup as well.
Unfortunately you have to target iOS 5.
https://developer.apple.com/library/ios/qa/qa1719/_index.html
Upvotes: 3