How to add files to an App Group shared directory? - ios

So I have an app with a share extension each with their own target. The core of the apps' bundle identifier is com.company.app and the share extensions is com.company.app.share. Each have their own entitlements file, with the core apps being App Groups -> group.com.company.app and the share extension being the same. Both of the bundle identifiers are listed on the apple developer website but for some reason,
I don't seem to have the right permissions to write to the shared private/var/mobile/Containers/Shared/AppGroup/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Caches/ directory.
The error message I get is:
You don’t have permission to save the file “Caches” in the folder “Library”.
The directory is being saved like this:
GetZipURLInItems(self.extensionContext.inputItems, ^(NSURL *url, NSError *error) {
if (error == nil) {
NSURL *containerURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:GroupIdentifier] URLByAppendingPathComponent:#"Library/Caches"];
NSLog(#"EXT: containerURL: %#", containerURL);
[WPZipArchive unzipFileAtPath:[url path] toDestination:[containerURL absoluteString]];
[self listDirectoryAtPath:[containerURL absoluteString]];
}});
What am I'm missing here?

The issue was actually using [url absoluteString] vs [url path], changing all the urls to path solved the permissions error.

Related

Why my app is not shown in iCloud Drive Folder

iCloud Drive Folder doesn't show my app's folder.
This is how I send a file to iCloud Drive:
- (IBAction)btnStoreTapped:(id)sender {
// Let's get the root directory for storing the file on iCloud Drive
[self rootDirectoryForICloud:^(NSURL *ubiquityURL) {
NSLog(#"1. ubiquityURL = %#", ubiquityURL);
if (ubiquityURL) {
// We also need the 'local' URL to the file we want to store
//NSURL *localURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Photo" ofType:#"pdf"]];
NSURL *localURL = [self localPathForResource:#"document" ofType:#"doc"];
NSLog(#"2. localURL = %#", localURL);
// Now, append the local filename to the ubiquityURL
ubiquityURL = [ubiquityURL URLByAppendingPathComponent:localURL.lastPathComponent];
NSLog(#"3. ubiquityURL = %#", ubiquityURL);
// And finish up the 'store' action
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) {
NSLog(#"Error occurred: %#", error);
}else{
NSLog(#"Succeed");
}
}
else {
NSLog(#"Could not retrieve a ubiquityURL");
}
}];
}
- (void)rootDirectoryForICloud:(void (^)(NSURL *))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
if (rootDirectory) {
if (![[NSFileManager defaultManager] fileExistsAtPath:rootDirectory.path isDirectory:nil]) {
NSLog(#"Create directory");
[[NSFileManager defaultManager] createDirectoryAtURL:rootDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(rootDirectory);
});
});
}
- (NSURL *)localPathForResource:(NSString *)resource ofType:(NSString *)type {
NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *resourcePath = [[documentsDirectory stringByAppendingPathComponent:resource] stringByAppendingPathExtension:type];
return [NSURL fileURLWithPath:resourcePath];
}
Actually I did everyting explained in this post
Everything looks like fine. I can upload files successfully and I can show them in my computer via terminal.
And I can see the files I just uploaded from my iphone.
But nothing shown in my iCloud Drive folder in my mac or icloud.com. Why they are not showing my app's folder even everything looks like fine and there is no error?
I have tried the same code in swift and got the same problem.
This is what worked for me :
1) Find the following lines in your info.plist source code
<key>CFBundleVersion</key>
<string>1</string>
2) Increase the version number. If it is 1, increase it to 2. Do the same whatever the version number is.
3) If needed, uninstall the app from your device and re-run it after following the above two steps
The only thing that worked for me (I had exactly the same issues) in an existing app (so I can't change the bundle ID) was to create a Documents subfolder under the URLForUbiquityContainerIdentifier folder and write all my files there.
Once I did this, they appeared in Finder on my Mac. However they did not appear in a Documents subfolder of my app's folder in iCloud Drive, they appeared directly in the app folder.
But nothing shown in my iCloud Drive folder in my mac or icloud.com.
Why they are not showing my app's folder even everything looks like
fine and there is no error?
Is this a new app or a newly created iCloud container?
Then the reason is: NSUbiquitousContainerIsDocumentScopePublic=true only gets in effect once the app (and the entitled iCloud container) has been gone through review and approval.
I can't remember where I read that "missing piece of information" (at least i didn't get it from the Apple documentation).
Another example from my experience, which seems to prove this: In an existing app (with the "old style - TeamIdentifierPrefix" iCloud container) i can make that container visible on iCloud drive. But a new container won't be visible in iCloud Drive "to the public" (even tough I can see it using Terminal.app on my Mac). Once the app (with the new container) has been approved and is available on the AppStore, you can play again with the Info.plist (and make the new container visible/hidden, ...)
Bump CFBundleVersion in Info.plist.
Create a new iCound container on https://developer.apple.com/account/ios/identifier/cloudContainer
Specify custom containers in Xcode. Untick the old containers and tick the new created container.
For me it was forgetting to add the above entries in the app's info.plist for the new container.
My case was that I have missed that your container at Info.plist should be wrapped to NSUbiquitousContainers dictionary.
I have fixed and increase build version, the folder did appear.
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com....</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>The name at iCloud folder</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>

Flag "do-not'backup" files in iOS Main Bundle

I uploaded an app to Appstore but got rejected due to files being backed up to iCloud, when only user generated content should be backed up.
The thing is that I have a json in MainBundle that I use only the first time to create and update CoreData. Once I update CoreData, the json is no longer needed. I tried a solution to delete this json file but I can't since it's not permitted by Apple to delete or edit content from the Main Bundle. I, then, leave the json unused and in the Main Bundle, but the app was rejected due to this file being backed up to iCloud.
I tried this solution below, that Apple gave me to solve this problem:
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
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;
}
I still can't solve the problem, because every time I try to use this code, I get this error:
NSUnderlyingError=0x15f87bb0 "The operation couldn’t be completed. Operation not permitted"
My understanding is that I can't flag the json as "do-not-backup" because I can't delete or edit any files that are in MainBundle. I tried arguing with Apple that this happens but got the same response from them.
Any ideas on how to solve this problem?

How to know if NSURLIsExcludedFromBackupKey is working?

I recently had my app rejected due to:
2.23: Apps must follow the iOS Data Storage Guidelines or they will be rejected
The reason for this is my app downloads a lot of image files from the web and writes them to the documents directory. So I do the following to try and remedy the situation:
NSURL *url = [NSURL URLWithString:stickerURL];
NSError *error = nil;
BOOL success = [url setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
if(!success)
{
NSLog(#"Error excluding %# from backup %#", [url lastPathComponent], error);
}
NSData *data = [NSData dataWithContentsOfURL:url];
My question is, how am I to know if NSURLIsExcludedFromBackupKey is working? The BOOL success is always coming back as YES. However, when I check Settings -> iCloud -> Storage & Backup -> Manage Storage -> MyiPhone5, it's making no difference in the size that is being shown underneath my app. Would this mean it's not working? If not, what can I fix in my code?
If you are checking "Manage Storage" in your device's settings, and after adding a bunch of files (eg., photos, documents, or whatever it is you're flagging to not be backed up) you notice that the backup size for you app is increasing, then the flag isn't "working" (although that shouldn't be the case, really).
One way to test it is to get rid of the key NSURLIsExcludedFromBackupKey, download a bunch of data, and then check to see if the backup size is increasing. Then, once you add the key back into your code, and once again download a bunch of data, the backup storage size should really not increase noticeably.

App does not store any content in document directory but Appstore reject

I am new to iOS development. My app got rejected from the review, stating the following reason,
2.23 Apps must follow the iOS Data Storage Guidelines or they will be rejected
We found that your app does not follow the iOS Data Storage Guidelines, which is required per the App Store Review Guidelines.
I am not storing my DB file in documents directory. Here's my code,
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [libraryPath stringByAppendingPathComponent:#"DatabaseFolder"];
NSURL *pathURL = [NSURL fileURLWithPath:path];
BOOL isDirectory = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
if (isDirectory) {
return pathURL;
} else {
// Handle error. ".data" is a file which should not be there...
[NSException raise:#"'Private Documents' exists, and is a file" format:#"Path: %#", path];
}
}
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]) {
[NSException raise:#"Failed creating directory" format:#"[%#], %#", path, error];
}
return pathURL;
How to reproduce a crash or bug that only App Review or users are seeing?
The iOS Data Storage guideline document (login required to view) says,
Everything in your app’s home directory is backed up, with the exception of the application bundle itself, the caches directory, and temp directory.
This means even your NSLibraryDirectory directory contents gets backed up to iCloud. To resolve this you have following options,
Use the /tmp directory for storage
Use the /Caches directory for storage
Only use the /Documents directory for user-generated content that cannot be re-created.
Set the do not backup attribute on the file using setResourceValue:forKey:error: method of NSURL.
Here is how you can mark a resource for not backing up to iCloud.
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
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;
}
Hope that helps!
I assume the reviewer doesn't like that you are storing the database in the library folder and there within one created by you. If you read the mentioned guidelines you'll see that you shouldn't store there.
Data that can be downloaded again or regenerated should be stored in the /Library/Caches directory. Examples of files you should put in the Caches directory include database cache files and downloadable content, such as that used by magazine, newspaper, and map applications
I had this problem for a while also. So I made a class to handle this for me. There are different rules of where you can store stuff in different OS's. So my class checked the OS and returned a proper data director for each one and even handled the migration of data from one location to the other if the OS was updated.
But pretty much today you could just support the 5.1 and up location and be fine.
The key is that you need to set your do not backup attribute also.
I just put in a github here: https://github.com/badweasel/BWFileManager

UIManagedDocument can only read documents that are file packages

My app is using a core data SQLite database. I would like to enable my users to use iCloud to sync it between devices - and I was thinking I could use UIManagedDocument.
I subclassed it, following Apple's documentation, and it is works when a new persistent store file needs to be created. However, when I try to use it to open my old persistent store file, I get the following exception thrown error:
"UIManagedDocument can only read documents that are file packages"
Does this mean that I need to migrate the old persistent store to a new store managed by UIManagedDocument? If so, do I need to do this manually (i.e. read each record one-at-a-time from the old store and write it into the new one)?
Thanks in advance!
UIManagedDocument creates packages(folders) rather than atomic stores. The store is still there but its buried in the package. If you right click on the file that is created in your Documents folder in the simulator you'll be able to see the structure. The default is
mydocument.foo
-> StoreContent
-> persistentStore
What you need to do is create a new extension for your app file type so for example if your database extension is .myappdb you need to create a new document type in your project settings which might be .myappdbw. You can copy all settings from the entry for .myappdb
Next at the point where you handle opening your legacy document at mydocumenturl instead of passing that to your persistent store co-ordinator you create the directory structure above.
NSURL *newurl = [[mydocumenturl URLByDeletingPathExtension] URLByAppendingPathExtension:#"myappdbw"];
NSURL *desturl = [newurl URLByAppendingPathComponent:#"StoreContent"];
[[NSFileManager defaultManager] createDirectoryAtURL:desturl withIntermediateDirectories:YES attributes:nil error:NULL];
NSURL *finalurl = [desturl URLByAppendingPathComponent:#"persistentStore"];
and then move the legacy database into the folder system you have created
[[NSFileManager defaultManager] moveItemAtURL:mydocumenturl toURL:finalurl error:NULL];
and then you can pass the bundle url to UIManagedDocument
UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:newurl];
A link which will be useful for the iCloud integration is
http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-iCloudCoreData/_index.html
Its all a bit mysterious as the most of the promised sample code has failed to appear so far but on the other hand its mostly fairly simple to deduce. Have a look at WWDC2011 sessions 107,116 and 315 for more hints.
But note that if you are going to use this method for migrating your legacy docs DONT set the NSPersistentStoreUbiquitousContentNameKey at point you migrate because the package changes when you do. The doc above describes it quite well.
Thanks for this tip. I think I found an even simpler solution.
I just create a new UIManagedDocument with a different filename than my old persistent store location.
In my subclassed UIManagedDocument, I override the configurePersistentStoreCoordinatorForURL method and do the migration once there:
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)storeURL ofType:(NSString *)fileType modelConfiguration:(NSString *)configuration storeOptions:(NSDictionary *)storeOptions error:(NSError **)error
{
// If legacy store exists, copy it to the new location
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:legacyPersistentStoreURL.path])
{
NSError* thisError = nil;
[fileManager copyItemAtURL:legacyPersistentStoreURL toURL:storeURL error:&thisError];
[fileManager removeItemAtURL:legacyPersistentStoreURL error:&thisError];
}
return [super configurePersistentStoreCoordinatorForURL:storeURL ofType:fileType modelConfiguration:configuration storeOptions:storeOptions error:error];
}

Resources