How to save downloaded files(audio, doc) automatically to user's Downloads folder in mac os x sandboxed application? - macos

In my Mac OS X application I tried to save downloaded files to application's directory(i.e. HomeDirectory()/Documents) but App Store rejected my application saying that your downloaded file is not accessible to the user easily( i.e. without opening the app). Then I tried to write the downloaded files to ~/Downloads folder by adding Read/Write permission in entitlements, but App Store again reject the application saying
Your application accesses the following location(s):
~/Download
The majority of developers encountering this issue are opening files
in Read/Write mode instead of Read-Only mode, in which case it should
be changed to Read-Only.
Other common reasons for this issue include:
creating or writing files in the above location(s), which are not valid locations for files to be written as stated in documentation.
writing to the above location(s) without using a valid app-id as a container for the written files.
Now the issue is App Store is neither allow me to save the files in App's Directory nor in System's folder(i.e. Downloads). Also I Don't want to use NSSavePanel every time. I want to download the files silently. Where should I save my files?

With the help of Security-Scoped Bookmark, user-selected read-write entitlement and NSOpenPanel I was abled to read/write to user selected folder.
Below are the steps I followed,
Added
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
in Entitlements file.
Asked the user to select(or create and select) the desired folder to which my application want to access(read/write) using NSOpenPanel.
When user selects the folder, I created the bookmark of selected folder path as bookmarked path in NSUserDefaults using NSURLBookmarkCreationWithSecurityScope.
NSOpenPanel *openDlg = [NSOpenPanel openPanel];
[openDlg setCanChooseDirectories:YES];
[openDlg setCanCreateDirectories:YES];
[openDlg setAllowsMultipleSelection:FALSE];
[openDlg setPrompt:#"Select"];
if ( [openDlg runModal] == NSModalResponseOK )
{
NSURL *url = openDlg.URL;
NSError *error = nil;
NSData *bookmark = [url
bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&error];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:bookmark forKey:#"DOWNLOAD_FOLDER_BOOKMARK_PATH"];
[userDefaults synchronize];
}
Once you saved the bookmarked path in NSUserDefaults you can access the saved path later using NSURLBookmarkResolutionWithSecurityScope.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSData * bookmarkedPathData = [userDefaults objectForKey:#"DOWNLOAD_FOLDER_BOOKMARK_PATH"];
NSURL* urlFromBookmark = [NSURL URLByResolvingBookmarkData:bookmarkedPathData
options:NSURLBookmarkResolutionWithSecurityScope
relativeToURL:nil
bookmarkDataIsStale:nil
error:&error];
Once you got the saved Bookmarked URL you can use that URL to perform read, write operation. Before reading/writing from/to the URL, please start the scope using [urlFromBookmark startAccessingSecurityScopedResource]; . And after finishing read/write operation stop the scope using [saveFolder stopAccessingSecurityScopedResource];
Note: I did tried to write to directly to Documents, Downloads, Desktop without creating folder inside these directories but Apple rejected the app, saying
Your application access the following locations 'Downloads'.
Then instead of writing directly to these directories( Documents, Downloads, Desktop), I asked the user to select(create & select) a folder, then performed the read/write operations on user selected folder using Security-Scope-Bookmark.
Hope this helps someone.

Related

iOS8: Sharing common files between apps

Two iOS: AppA and AppB
Both Apps are created by me and I would like to share one single file between both of the apps.
i.e. AppA launches on deviceA and the User saves data on fileA. Later the User launches AppB on the same (deviceA) and also saves data on fileA. Both apps are saving data on the same file.
I'm aware that I can use NSUserDefaults and share Keychain between apps, but that's not what I'm looking for.
I read up on document extensions provider and app groups, but I'm confused if I can use these for this scenario? Or is there any other way to accomplish this?
You can do it using Application Group Container Directory:
NSFileManager *fm = [NSFileManager defaultManager];
NSString *appGroupName = #"Z123456789.com.example.app-group"; /* For example */
NSURL *groupContainerURL = [fm containerURLForSecurityApplicationGroupIdentifier:appGroupName];
NSError* theError = nil;
if (![fm createDirectoryAtURL: groupContainerURL withIntermediateDirectories:YES attributes:nil error:&theError]) {
// Handle the error.
}
You could just upload the files after saving to your server and make both apps request updates for the file whenever they are launched.
Hope that helps :)

dismissGrantingAccessToURL of UIDocumentPickerExtensionViewController is not working

I am working on document provider extension for import mode.
URL of file is sitting inside the shared container shared by both extension & container app.
I got assertion failure saying that it should sit inside shared container/File Provider Storage directory.
So I copied file from original directory to File Provider Storage directory.
I have a file in original location & I got a file in new copied location also. I checked for data length of the file also, Its proper ,
But when I call [self dismissGrantingAccessToURL:toUrl];
Its not dismissing UIDocumentPickerExtensionViewController,
I am not facing any exceptions , but it wont dismiss and initiate the transfer, So user can still access the extension.
Below is my code, If anyone came across the same thing please leave your reply.
- (void)userChoosesEntityOfUrl:(NSURL *)url
{
NSURL *toUrl = [self.documentStorageURL URLByAppendingPathComponent:[url lastPathComponent]];
if ([[NSFileManager defaultManager] fileExistsAtPath:[toUrl path]]) {
[[NSFileManager defaultManager] removeItemAtPath:[toUrl path] error:nil];
}
if ([[NSFileManager defaultManager] copyItemAtURL:url toURL:toUrl error:nil]) {
NSLog(#"%#", [toUrl path]);
}
if (![[NSFileManager defaultManager] fileExistsAtPath:[toUrl path]]) {
NSLog(#"File Doesn't exists at this path");
return;
}
NSLog(#"Data Length %i",[[NSData dataWithContentsOfFile:[toUrl path]] length]);
[self dismissGrantingAccessToURL:toUrl];
}
When debugging your implementation of NSFileProviderExtension, you usually end up stopping/killing it using Xcode. After this, iOS often has problems restarting your file extension. This results in your document picker not being dismissed after calling dismissGrantingAccessToURL:. This bug also affects your subclass of UIDocumentPickerExtensionViewController which sometimes won't start (you only see the navigation bar of the document picker but not the content).
The workaround is to reboot your device.
This will occur if you have a backing File Provider extension that has not been fully implemented. Remove the File Provider target from your embedded extension phase, set your document extension only support import/export modes in the Info.plist, and do a product -> Clean before building and running your extension.

NSUserDefaults of different application

I am currently developing System Pane and my app have some configuration settings saved to User Defaults:
NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:listOfStuff];
[userDefault setObject:encodedObject forKey:#"myStuff"];
[userDefault synchronize];
Can anyone tell me if and how a different application can read settings that have been saved in above System Pane?
Thank you.
The way to read someones preferences is very simple and straight forward:
NSUserDefaults *defaults = [[NSUserDefaults alloc] init];
[defaults addSuiteNamed:#"com.apple.systempreferences.plist"];
NSLog(#"DefaultExposeTab is: %#", [defaults stringForKey:#"DefaultExposeTab"]);
Make sure to initialize NSUserDefaults following way:
[[NSUserDefaults alloc] init];
then you can add desired preference list, in our case I would like to read System Preferences:
[defaults addSuiteNamed:#"com.apple.systempreferences.plist"];
and finally get value for whichever key you want, in this example:
"DefaultExposeTab"
Above example works like a charm. Please remember it will only work for current user.
Thanks.
P.S: Please note - above example will NOT work for sandboxed application.
As Melr explained, you need to use the suite name. But it's actually possible to read the preferences of another application from a sandboxed app. It requires a temporary exception entitlement.
For example, to read the Finder preferences, you need to add this to your
entitlements file:
<key>com.apple.security.temporary-exception.shared-preference.read-only</key>
<array>
<string>com.apple.finder</string>
</array>
Then you can access the Finder preferences like this:
NSUserDefaults *finderDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"com.apple.finder"];
NSDictionary *standardViewOptions = [finderDefaults dictionaryForKey:#"StandardViewOptions"];
NSNumber *fontSize = [standardViewOptions valueForKeyPath:#"ColumnViewOptions.FontSize"];
If you need to read the preferences of another app, just replace com.apple.finder with the appropriate bundle identifier.
My app Prefs Editor uses the CFPreferences functions for this (e.g. CFPreferencesSetAppValue). That gives you direct control over whose app's settings you'll access.
You can even access the prefs of sandboxed applications with this method, if you know their plist file's full path and pass that to the applicationID parameter.
For more info, read through the answers of this question: How does OS X's defaults command get access to prefs of sandboxed apps?
Yes another application can read the settings if you save by using NSUserDefaults because it saves the settings as an unencrypted plist file on disk.
Reading another application's settings from a plist file is easy. Search for the file path and open it. NSDictionary can be initialized with a string of the file path to a plist file.
If the defaults you want to store are secret (like password, security tokens, etc), then add an encrypted item to the keychain instead of storing them in a plist.
https://developer.apple.com/library/mac/documentation/security/Conceptual/keychainServConcepts/01introduction/introduction.html

How to set up initial Core Data store in a Mac app?

I am an experienced iOS developer trying to make my first Mac app. I want to use Core data to store the data in my app. In my iOS apps, I generally have a pre-created SQLite file which is used as the initial state of the data store, and which is moved into place on the first time the app is run, like this:
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Datafile.sqlite"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"Datafile-DefaultData" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
I want to do something similar in the mac app, except put the data in the ~/Library/Application Support/MyApp directory. I can't seem to figure out how to do it. Any pointers?
In Xcode 4, the default Core Data project template, for some reason, uses the root ~/Library directory (ie ~/Library/Application to store your Core Data file. You should change this anyways (because it's a bad idea), but once you do that, it should work as you expect. I believe the default name on the Mac is storedata, and you should also note that you'll need to change the store type from XML, which is the default.

Cocoa equivalent of .NET's Environment.SpecialFolder for saving preferences/settings?

How do I get the reference to a folder for storing per-user-per-application settings when writing an Objective-C Cocoa app in Xcode?
In .NET I would use the Environment.SpecialFolder enumeration:
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
What's the Cocoa equivalent?
In Mac OSX application preferences are stored automatically through NSUserDefaults, which saves them to a .plist file ~/Library/Preferences/. You shouldn't need to do anything with this file, NSUserDefaults will handle everything for you.
If you have a data file in a non-document based application (such as AddressBook.app), you should store it in ~/Library/Application Support/Your App Name/. There's no built-in method to find or create this folder, you'll need to do it yourself. Here's an example from one of my own applications, if you look at some of the Xcode project templates, you'll see a similar method.
+ (NSString *)applicationSupportFolder;
{
// Find this application's Application Support Folder, creating it if
// needed.
NSString *appName, *supportPath = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSApplicationSupportDirectory, NSUserDomainMask, YES );
if ( [paths count] > 0)
{
appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleExecutable"];
supportPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:appName];
if ( ![[NSFileManager defaultManager] fileExistsAtPath:supportPath] )
if ( ![[NSFileManager defaultManager] createDirectoryAtPath:supportPath attributes:nil] )
supportPath = nil;
}
return supportPath;
}
Keep in mind that if your app is popular you'll probably get requests to be able to have multiple library files for different users sharing the same account. If you want to support this, the convention is to prompt for a path to use when the application is started holding down the alt/option key.
For most stuff, you should just use the NSUserDefaults API which takes care of persisting settings on disk for you.

Resources