Temporary Files in Sandboxed Mac Apps

The Mac App Store is great, but isn't without its challenges, and chief amongst them is Sandboxing. An app from the App Store has very limited access to a user's computer. It has to be given specific permission to get to the camera, existing photos, contacts, and even files. The files restriction is amongst the most onerous as far as developers are concerned. If a user does not grant explicit access to a file, or to a directory of files, the app can not get to them.

This file sandboxing presents a special challenge when it comes to temporary files. If your app needs to create a temporary file for some reason, say for scratch space during computation, the user has to grant access to this file. This is not a very user-friendly process. Can you image prompting the user to “Please allow this program to create the file IMG9143.jpg_temp_file”? Most users would give up and shut the app down.

Luckily for us, Apple has created a workaround, called “Related Items” (search for “Related Items” on that page). While not specifically designed for temporary files, Related Items work great for that purpose, as long as your temporary file meets a very specific set of criteria. The temporary file must have the same name (minus extension) as a file that the user has given access to (by selecting it in an “Open” dialog box, for instance), and the temporary file's new extension must be known during the development process. In other words, no runtime-generated temp file extensions.

Code

As long as your proposed temporary file meets these criteria, we can use “File Presenters” to enable the temporary file in a sandboxed app. We'll need one new class implementing the File Presenter protocol. I've called my class “WRFilePresenter” (the WR is for “WindUp Rocket“).

First the header file:

//
//  WRFilePresenter.h
//
//  Created by Matt Welch on 3/17/14.
//

#import 

@interface WRFilePresenter : NSObject 

-(void) setURLs:(NSURL*)url;
@end

And the main file:

//
//  WRFilePresenter.m
//  Bearings
//
//  Created by Matt Welch on 3/17/14.
//

#import "WRFilePresenter.h"

@implementation WRFilePresenter
{
    NSOperationQueue* queue;
    NSURL* pFileURL;
    NSURL* tFileURL;

}

- (id) init {
    self = [super init];
    if (self) {
        queue = [NSOperationQueue new];
        [NSFileCoordinator addFilePresenter:self];
    }
    return self;
}

- (NSURL*) primaryPresentedItemURL {
    return pFileURL;
}

- (NSURL *) presentedItemURL {
    return tFileURL;
}

- (NSOperationQueue*) presentedItemOperationQueue {
    return queue;
}

-(void) setURLs:(NSURL*)url {
    pFileURL=url;
    NSString *fURLS=[url absoluteString];
    NSString *fURLSt=[NSString stringWithFormat:@"%@%@",fURLS,@"_temp_file_extension" ];

    NSURL *surl = [NSURL URLWithString:fURLSt];

    tFileURL=surl;
}

@end

A File Presenter class must provide three attributes:

  1. primaryPresentedItemURL ”“ The URL for the original file
  2. primaryItemURL ”“ the URL for the temporary file
  3. presentedItemOperationQueue ”“ the queue on which the app will perform file presentation tasks

My class has an additional method which takes as an argument a file URL (which should be the URL for the original, non-temporary file), and populates two properties, a URL that will be returned for the primaryPresentedItemURL, and a new URL that will be returned for primaryItemURL. The difference between the two URLs is that the second one has our temporary file extension, in this example, “_temp_file_extension” appended to the existing extension.

To implement this class in code, it's a simple two-line process:

WRFilePresenter *filePresenter=[[WRFilePresenter alloc] init];
[filePresenter setURLs:realFileURL];

The “realFileURL” variable above is simple a variable of type NSURL that points to our main file. Executing these two lines for every file for which you need to create a sibling temp file is all that's necessary to get around the sandboxing restrictions, at least on the code side.

Xcode Target Info

There is one more thing we need to do in Xcode to get things set up. In Xcode, select your Target, at the top of the “Project Navigator”, and in the main window, select the “Info” tab. There, you'll find a section for “Document Types”. Open the twisty, and click on the “+” sign to add a new type. It should look something like this:

Xcode temporary filesThe important sections are the “Extensions” and the “Additional document type properties”. For the extensions, enter all the temporary extensions you think your app my use. In our example, I've built my file presenter to append “_temp_file_extension” to the original file to anticipate a temporary file name, so here I've entered “doc_temp_file_extension”, meaning that I can now create temporary files for any master file with a “.doc” extension.

For “Additional document type properties”, enter a key of “NSIsRelatedItemType” and set the boolean value to yes.

Once these steps have been completed, you can create and access appropriately named temporary files just as you might in a regular, non-sandboxed application.

Let me know if you have any questions.