Open any folder with NSDocument
Asked Answered
S

4

7

I am trying to write an application that can open any folder in the NSDocument subclass but can't figure out the right Info.plist settings. It is important that my app should not use bundles, neither folders with a particular file extensions, just be able to open any folder.

What I tried:

  • If I set the document type extension to empty string then the file open panel does not allow any file to be selected
  • If I set the document type extension to * then the file open panel does enable all files but not the folders: folders are opened as in finder
  • If I set the folder extension to the document type extension I can open the folder in the file open dialog as a document (this is what I want) but I restrict my solution to the folders with that extension
  • By setting OSType to "fold", document type identifier or name to "public.folder" etc. as I read in regarding forums has no visible effect for me.

How can I open any folder in open file dialog?

Sharpwitted answered 20/12, 2012 at 10:15 Comment(0)
P
3

You probably can't do this without writing some custom code.

You need to present an NSOpenPanel manually, like this:

NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setCanChooseFiles:NO];
[panel setCanChooseDirectories:YES];

[panel beginSheetForDirectory:nil
                         file:nil
               modalForWindow:[self window]
                modalDelegate:self
               didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
                  contextInfo:nil];

An open panel presented in this way will let the use choose any directory they wish. You can implement NSOpenPanel's delegate methods to validate each folder and en/disable if if you need to.

Pic answered 20/12, 2012 at 13:30 Comment(0)
S
6

For completeness here are some more details to @iKenndac's answer:

In IB check which method of First Responder is associated with the File / Open... menu item. In my case it was openDocument:. Implement this method in the AppDelegate:

-(void)openDocument:(id)sender
{
    NSOpenPanel *panel = [NSOpenPanel openPanel];
    [panel setCanChooseFiles:NO];
    [panel setCanChooseDirectories:YES];
    [panel setAllowsMultipleSelection:NO];

    [panel beginSheetModalForWindow:nil
                  completionHandler:^(NSInteger result) {
                      if (result == NSFileHandlingPanelOKButton) {
                          NSURL* selectedURL = [[panel URLs] objectAtIndex:0];
                          NSLog(@"selected URL: %@", selectedURL);
                          NSError* error = nil;
                          [[NSDocumentController sharedDocumentController] 
                              openDocumentWithContentsOfURL:selectedURL 
                                                   display:YES 
                                                     error:&error];
                      }
                  }];
}

You still need to define a Document Type in the Info.plist, setting the Identifier (LSItemContentTypes) field to public.folder.

Sharpwitted answered 20/12, 2012 at 14:48 Comment(1)
Yes, except I think you should do that in an NSDocumentController subclass instead.Spirograph
P
3

You probably can't do this without writing some custom code.

You need to present an NSOpenPanel manually, like this:

NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setCanChooseFiles:NO];
[panel setCanChooseDirectories:YES];

[panel beginSheetForDirectory:nil
                         file:nil
               modalForWindow:[self window]
                modalDelegate:self
               didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
                  contextInfo:nil];

An open panel presented in this way will let the use choose any directory they wish. You can implement NSOpenPanel's delegate methods to validate each folder and en/disable if if you need to.

Pic answered 20/12, 2012 at 13:30 Comment(0)
E
3

Just as an updated summary of how one does that today, here's a step-by-step guide of what I had to do:

  1. Create an application project from the Cocoa Application template using Xcode.

  2. Check Create Document-based Application and leave whatever it suggests there for the "Document Extension" (it will refuse to enable the "Next" button if you delete the filename extension it right here, so we'll do that later).

  3. Click your project's icon, go to the Info tab and to Document Types.

  4. Delete the contents of the Extensions field. Our folders don't need a particular filename suffix

  5. Write public.folder into the Identifier field.

  6. Under Additional document type properties in the CFBundleTypeOSTypes array add one entry, fold (just those four lowercase letters). Not sure if that is necessary, but it's at least correct.

  7. Make sure document is distributed as a bundle is not checked.

  8. Create an NSDocumentController subclass containing the following method to your project. Name it E.g. ULIFolderDocumentController.

    -(void)openDocument:(id)sender
    {
        NSOpenPanel *panel = [NSOpenPanel openPanel];
        [panel setCanChooseFiles:NO];
        [panel setCanChooseDirectories:YES];
        [panel setAllowsMultipleSelection:NO];

        [panel beginWithCompletionHandler: ^( NSInteger result )
        {
            if (result == NSFileHandlingPanelOKButton)
            {
                NSURL* selectedURL = [[panel URLs] objectAtIndex:0];
                NSLog(@"selected URL: %@", selectedURL);
                [self openDocumentWithContentsOfURL: selectedURL
                        display: YES
                        completionHandler: ^(NSDocument * _Nullable document, BOOL documentWasAlreadyOpen, NSError * _Nullable error)
                        {
                            NSLog(@"%spened document %@ (%@)", (documentWasAlreadyOpen? "Reo" : "O"), document, error);
                        }];
            }
        }];
    }
  1. Add a line to your app delegate's -init method that loads your subclass instead of NSDocumentController. This is easy, just request the shared object:
    [ULIFolderDocumentController sharedDocumentController]; // Override system's NSDocumentController with ours.
  1. Try it! :)
Episiotomy answered 17/4, 2016 at 12:50 Comment(3)
2022 update: this basically still works, with a few minor differences: - Some of the changes you need to make in the Document Types section are now in the Imported Type Identifiers section. Looks like the identifier is duplicated between the two sections, too. - I didn't manage to add an Additional document type properties, but it seems to work anyway. - I didn't find Document is distributed as a bundle anywhere, but it seems to work anyway. So mostly, just follow the instructions as well as you can and they'll still work!Jowl
A couple more notes: - In the NSDocument subclass, replace the read(from data: Data, ofType typeName: String) method with a read(from url: URL, ofType typeName: String) method, since you'll be reading from a folder on disk, rather than having macOS read the data for you - In NSDocumentController.h, Apple suggests instantiating your custom document controller from applicationWillFinishLaunching:, rather than from init:—not sure what difference that makesJowl
I suppose -applicationWillFinishLaunching: may be the latest point before any documents are actually loaded that you might run this code, AFAIR it is called before the event loop is started. The reason I picked -init in the delegate is because it gets called before Cocoa creates any UI or receives/processes any odoc Apple Events. Both methods guarantee that.Episiotomy
C
0

Here's what I had to do in a new document-based application project:

Info.plist (or Target > Info > Document types)

Document type identifier: public.directory, Role: Editor, Class: $(PRODUCT_MODULE_NAME).Document

(It's important not to set the Role to Viewer, because it's documented in DocumentController.defaultType that only Editor roles can create untitled documents, and an error is shown when trying to open one.)

AppDelegate.swift

class AppDelegate: NSDocumentController, NSApplicationDelegate {

    override func runModalOpenPanel(_ openPanel: NSOpenPanel, forTypes types: [String]?) -> Int {
        openPanel.canChooseDirectories = true
        return super.runModalOpenPanel(openPanel, forTypes: types)
    }

}

Document.swift

class Document: NSDocument {
    
    override func makeWindowControllers() {
        let windowController = ...
        self.addWindowController(windowController)
    }

    override func read(from url: URL, ofType typeName: String) throws {
        // read contents of url
        DispatchQueue.main.async { [self] in
            // update view controllers
        }
    }
    
    override func write(to url: URL, ofType typeName: String) throws {
    }

}
Chil answered 11/3, 2023 at 11:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.