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)
Related
We have an iOS application for the iphone that is having strange behavior. I store some data of the app in an SQLite Database file in the documents folder. Every once in a while i realize that when the application crashes the file on the disk gets deleted. And i can´t figure out why the app would do this...Any Ideas?!
Make sure that you're saving any documents for the application to the application's private folder in the documents. I use this:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainask, YES) lastObject];
Found the Solution...If i save my database in the Cache Directory instead of the Documents directory, it works...:)
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'm struggling a bit with the idea of iCloud and posted a more general question here. My biggest problem is to decide whether I should stop putting the user's data in the good old documents folder which is found in the app's sandbox. To illustrate my problem:
The docs don't give an answer as far as I can see. Let's suppose I have an App which handles different txt files. Once I start my app, I simply check if any txt files are in the cloud like so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"AppDelegate: app did finish launching");
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
// (1) iCloud: init
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(#"User has iCloud enabled! Let's get the txt files from the cloud.");
[self loadDocument];
} else {
NSLog(#"User doesn't have iCloud enabled. App is thus worthless.");
}
return YES;
}
I then have a method to check if there are any txt files in the cloud and if so, load them. If not, I simply create new txt files in the cloud.
This means that the app does not store any data in the documents folder. As far as I understand it, everything is either in the local iCloud storage of my device (which is also accessible if the user is OFFLINE) or in the cloud. So the text file exists in two places: on my device and the cloud.
So there is simply no need to store a third copy in my local documents folder, right? Or is this essential for some reason I have overlooked? In other words, for what should I use the local documents folder if I offer iCloud to my users? (And can I simply ignore those who won't sign up for iCloud?)
EDIT: Just to clarify, when I'm talking about the standard documents folder in the app's sandbox, I mean this one:
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
Perhaps I'm a bit slow, by re-reading the docs for the 4th or 5th time, I came across this which suggests that you should always create your files in the sandbox and then move them to the cloud. So in a way, Apple suggests to have 3 versions of the same file at all times:
Apps use the same technologies to manage files and directories in
iCloud that they do for local files and directories. Files and
directories in iCloud are still just files and directories. You can
open them, create them, move them, copy them, read and write from
them, delete them, or any of the other operations you might want to
do. The only differences between local files and directories and
iCloud files and directories is the URL you use to access them.
Instead of URLs being relative to your app’s sandbox, URLs for iCloud
files and directories are relative to the corresponding iCloud
container directory.
To move a file or directory to iCloud:
Create the file or directory locally in your app sandbox. While in
use, the file or directory must be managed by a file presenter, such
as a UIDocument object.
Use the URLForUbiquityContainerIdentifier: method to retrieve a URL
for the iCloud container directory in which you want to store the
item. Use the container directory URL to build a new URL that
specifies the item’s location in iCloud. Call the
setUbiquitous:itemAtURL:destinationURL:error: method of NSFileManager
to move the item to iCloud. Never call this method from your app’s
main thread; doing so could block your main thread for an extended
period of time or cause a deadlock with one of your app’s own file
presenters. When you move a file or directory to iCloud, the system
copies that item out of your app sandbox and into a private local
directory so that it can be monitored by the iCloud daemon. Even
though the file is no longer in your sandbox, your app still has full
access to it. Although a copy of the file remains local to the current
device, the file is also sent to iCloud so that it can be distributed
to other devices. The iCloud daemon handles all of the work of making
sure that the local copies are the same. So from the perspective of
your app, the file just is in iCloud.
All changes you make to a file or directory in iCloud must be made
using a file coordinator object. These changes include moving,
deleting, copying, or renaming the item. The file coordinator ensures
that the iCloud daemon does not change the file or directory at the
same time and ensures that other interested parties are notified of
the changes you make.
See here.
There is no reason to store documents both in local storage as well as in iCloud. However, you should give users the option of turning iCloud storage off. With iCloud storage off, you should only look for files in local storage (as with pre-iOS5 apps). The best thing is to try to isolate the part of your code that needs to know where documents are stored, and have it test whether or not iCloud is available and enabled and have that piece of code return the URL where documents should be stored.
Update:
If you want to diverge for iOS5 vs. iOS4, you just need to test if the iOS5 features are there. There are several ways that you can do this. One way is to check:
if ([UIDocument class] == nil)
On iOS4 this will be true, and on iOS5 this will be false. I don't know what kind of datastructures you have for your files, but one thing you could do is create a wrapper around UIDocument. Inside this wrapper class, you could have instance variables for a UIDocument structure as well as for fields you would need in IOS4 (such as the path to the file). When you instantiate your class, test whether iCloud is enabled and if UIDocument is available, and if so, use it and set the field. Otherwise, set the other fields and leave the UIDocument field to be nil. When you need to do operations on your "file", test if the UIDocument field is nil, and if it is do it the "old" way. Otherwise, just pass on the request to the UIDocument object.
Remember that if the user has enabled device backup to iCloud, the documents directory gets backed up anyways.
It really depends on if your planning to use iCloud as a sync tool between apps / platforms. If your not, it really doesn't make sense to use iCloud to store your documents.
I display text and images in a UIWebView. Content isn't always the same. I access images within the content using the bundle path. For an inbetween versions update of content, I'd like to allows users the ability to download new content (text & images). This new content will also display in a UIWebView. The problem is I will have to use a disk path rather than my common pattern of using the bundle path. Unless there is a way to repackage the image at runtime into the bundle.
Once the next app store update for the app is availble, all of the previously downloaded images will be in the app bundle. On this update, I'll write overwrite the previous content and use the bundle path for images. Content will be exactly the same minus the image path.
Can anyone give some insight into how this might work or a better approach?
So far as I know you cannot repackage the bundles on the iPhone once your app has been released to the App Store. So go the other way, and put the data from the bundle on the filesystem so you can change it at runtime.
My usual technique for this stuff is:
bundle up the initial data
have a routine that checks for the presence of a versioned file on the iPhone's filesystem at startup
if that routine doesn't find the current version of the file, copy all the data into the iPhone's filesystem
reference the data from the filesystem in my app, rather than using the bundle path
So, essentially your bundle is just a delivery mechanism, a way to preload the filesystem with the stuff you are going to need. Once it's on the filesystem you can change anything you wish.
Agree with Benjamin - you cannot change your bundle contents.
Instead you can (and should) save your downloaded contents to Documents folder of your application sandbox. You can get the path to it this way:
// Look in Documents for an existing plist file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
Moreover contents of this folder persists during updates so it may be not necessary to put downloadable optional contents to the application bundle.