Nssavepannel - How to Restrict User to Only Save One One Set Directory

NSSavePannel - how to restrict user to only save one one set directory?

The thing is, the folder is already fix fixed within the app.

This is the time to acquire permission from the user to access this folder. Use a open (not save) dialog to have the user confirm selection of the folder. Think of this as a "confirm access dialog", you can:

  • Change the label of the "Open" button to something else using prompt
  • Set the title and message so the dialog is clearly a confirmation dialog
  • set the initial folder using directoryURL to the parent of the one you want confirmed (Note: any changes to directoryURL after the dialog is up are ignored so you cannot lock the dialog to that folder using the delegate didChangeToDirectoryURL – in the early sandbox you could but Apple has now stopped that)
  • Set the delegate and use its shouldEnable and validate callbacks to make sure only the folder you wish to have confirmed can be selected or the dialog cancelled.
  • Set canCreateDirectories & canChooseFiles to false, canChooseDirectories to true

Once the user has confirmed the folder access save a security scoped bookmark in your app's prefs. Your app can now regain access to that folder at any time. With that permission you can create and open files and folders within that folder without using NSOpenPanel or NSSavePanel again.

From this point to restrict users to saving in that folder put up your own dialog to ask for just the filename, omitting the path part, and bypass NSSavePanel –you can impersonate the standard dialogs or design your own from scratch.

HTH

Restrict access to certain folders using NSOpenPanel

Note that NSOpenPanel inherits from NSSavePanel, which in turn defines a delegate and a corresponding delegate protocol NSOpenSavePanelDelegate. You can use the delegate to extend the behaviour of the open panel so as to include the restriction you’ve listed in your question.

For instance, assuming the application delegate implements the open panel restriction, make it conform to the NSOpenSavePanelDelegate protocol:

@interface AppDelegate : NSObject <NSApplicationDelegate, NSOpenSavePanelDelegate>
@end

In the implementation of your application delegate, tell the open panel that the application delegate acts as the open panel delegate:

NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setDirectory:NSHomeDirectory()];
[openPanel setCanChooseDirectories:NO];
[openPanel setDelegate:self];
[openPanel runModal];

And implement the following delegate methods:

- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url {
NSString *path = [url path];
NSString *homeDir = NSHomeDirectory();

return [path hasPrefix:homeDir] && ! [path isEqualToString:homeDir];
}

- (void)panel:(id)sender didChangeToDirectoryURL:(NSURL *)url {
NSString *path = [url path];
NSString *homeDir = NSHomeDirectory();

// If the user has changed to a non home directory, send him back home!
if (! [path hasPrefix:homeDir]) [sender setDirectory:homeDir];
}

- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError {
NSString *path = [url path];
NSString *homeDir = NSHomeDirectory();

if (![path hasPrefix:homeDir]) {
if (outError)
*outError = ; // create an appropriate NSError instance

return NO;
}
return YES;
}

With NSSavePanel, how can the user choose a specific file type to save?

You can add a user-defined NSView to the NSSavePanel using setAccessoryView:, see Apple's docs. There is also an Apple sample Custom Save. You add your format selection controls to this accessory view.

Cocoa Swift: Save multiple NSImage files to the chosen url or path WITH the sandbox on

You need to use NSOpenPanel to obtain a destination folder from your user. Set the options so that the user can only select folders and can create new folders. You will get returned a URL to the existing or newly created folder. You can now create as many files (and subfolders) as you wish within the folder.

Is it possible to subclass NSSavePanel?

Short Answer: No

Longer Answer: Here Be Dragons

It is possible but stuff will probably break. You can also add methods using a category and they might work, or they might not. The problems arise due to the way NSOpenPanel is implemented to support the App Sandbox - various chicanery is going on behind the scenes and even convenience category methods which just call existing methods on the class can result in errors being reported by OS X and dialogs not to appear. NSOpenPanel is a delicate creature that should be touched as little as possible and only ever with great care.

Wrapping an NSOpenPanel instance in another class is a different story and should not upset it at all. Go that route.

Addendum re: Comment

The declaration of beginSheetModalForWindow is:

- (void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger result))handler

The completion handler gets passed a value indicating which button was pressed. To take action dependant on that you can use a standard if:

NSOpenPanel *openPanel;
NSWindow *hostWindow;
...
[openPanel beginSheetModalForWindow:hostWindow
completionHandler:^(NSInteger returnCode)
{
if (returnCode == NSFileHandlingPanelOKButton)
{
// OK pressed
...
}
else
{
// Cancel pressed
...
}

}
];

In Cocoa: How to set permission to read/write to a folder?

You've hit a long standing concern over the sandbox model; that of supporting applications which transform an input document to an output document in the same location.

Apple addressed a subset of the issue with the NSIsRelatedItemType entry for document types in an application Info.plist. If this flag is present and YES in multiple document type entries then your application when given access to a file of one of those types is given the right to create a file of any of the other types.

For example, TextEdit has this flag set for .txt, .rtf and .rtfd file types so once given access to a file of any of these types it can create a file of the same name but of any of the other types without further user permission required. It enables command such as "Convert to Plain Text".

It appears from your question that you are not doing this, but say, converting "image.png" to "image-resized.png", i.e. changing the name rather than the type. That is not supported without the application obtaining further permissions from the user.

The best workaround you have is to ask the user for permission to the enclosing folder of the files they wish to resize. Once the application has that it may perform any number of file creations within that folder - just as you observed for the entitlements which give access to certain special folders.

One process you can follow is:

1 - When needing to create the output file check if the application has access to the containing folder - you can use the access function for this (see OS X manual section 2 e.g. "man access" in the Terminal or search for access in Xcode's docs).

3 - (Yes I missed 2, see below) If you don't have access then request it from the user for the containing folder - throw up a suitable NSOpenPanel prompting them to select the containing folder with an explanation as to why.

4 - Once you have been granted access save it - store a security-scoped bookmark to that folder in a file in the applications "Application Support" directory. You can "prune" your cache as you build it - a bookmark to any folder removes the need for a bookmark to any folder below it in the filesystem tree. Doing this enables:

2 - If you don't have access check your cache of saved bookmarks and see if you have one for the folder, if so activate it. Note that this check is for a bookmark for the containing folder or any of its parent folders in the filesystem tree.

Using this process over time you will accumulate access to the folders your user uses. You should provide a way for users to clean the cache.

It's not as bad as it sounds! HTH.

Get File URL Path from Default NSSavePanel

NSSavePanel *panel = [NSSavePanel savePanel];

[panel setMessage:@"Please select a path to create a new database."]; // Message inside modal window
[panel setAllowedFileTypes:[[NSArray alloc] initWithObjects:@"sqlite3", @"sqlite", @"db", nil]];
[panel setAllowsOtherFileTypes:YES];
[panel setExtensionHidden:NO];
[panel setCanCreateDirectories:YES];
[panel setTitle:@"Create a database"]; // Window title
[panel setNameFieldStringValue:@"Untitled.sqlite3"];

[panel beginWithCompletionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton) {
path = [[panel URL] path];
url = [panel URL];
}
}];


Related Topics



Leave a reply



Submit