4

My sandboxed macOS app imports image files selected by the user via an NSOpenPanel modal window, as is customary.

At first, I configured the panel to canChooseDirectories = false, and set the allowedFileTypes property to NSImage.imageTypes. So far so good.

Using the app, I realized that the images I want to import are more often than not all grouped inside a folder with nothing more in it. It would be great if I could have the user just select the containing folder and import the images within "wholesale", so I adopted this code:

let panel = NSOpenPanel()

panel.allowsMultipleSelection = true
panel.canChooseDirectories = true
panel.canCreateDirectories = false
panel.canChooseFiles = true
panel.allowedFileTypes = NSImage.imageTypes

panel.begin { [unowned self] (result) in
    guard result == .OK else {
        return // User cancelled
     }

     // Read all selected images:

     let urls: [URL] = {
        if let directory = panel.directoryURL {
            // .........................................
            // [A] One directory selected:

            do {
                let urls = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [])
                return urls
            } catch {
                // (I ALWAYS END UP HERE)

                print(error.localizedDescription)
                return [] 
            }
        } else {
            // .........................................
            // [B] One or more files selected:

            return panel.urls
        }
     }()

 // (next: read individual urls...)

...but the try statement always fails, the catch block is executed and the error thrown is:

"The file “MyImageFolder” couldn’t be opened because you don’t have permission to view it."

Is there a way around this for sandboxed apps? Anything that I am forgetting, that will allow me to read the contents of a user-selected folder?


Addendum: Apple's documentation states that:

When a user of your app specifies they want to use a file or a folder, the system adds the associated path to your app’s sandbox. Say, for example, a user drags the ~/Documents folder onto your app’s Dock tile (or onto your app’s Finder icon, or into an open window of your app), thereby indicating they want to use that folder. In response, the system makes the ~/Documents folder, its contents, and its subfolders available to your app.

(emphasis mine)

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189

1 Answers1

1

I accepted @vadian's quick answer a bit prematurely, but it seems that I can access the individual files inside the user-selected folder from NSOpenPanel.

After reading this answer (that I somehow missed at first in my searches), I found out that the code below works:

// Warning! This code does not deal with the user selecting 
// multiple folders!

let urls: [URL] = {

    if inputURLs.count == 1, inputURLs[0].hasDirectoryPath {
        // Folder; Read its contents:
        do {
            let urls = try FileManager.default.contentsOfDirectory(at: inputURLs[0], includingPropertiesForKeys: nil, options: [])
                return urls

        } catch {
            // (todo: Handle Errors)
            return []
        }
    } else {
        // One or more images; Read them directly:
        return inputURLs
    }
}()

An additional mistake I seemed to be making is to use NSURL's isFileURL property to distinguish between a folder selected and a single file: it returns true for folder too!

So after I switched from using panel.directoryURL to using panel.urls[0] (when isFileURL is true), my app was trying to read a single image from the directory URL. No sandbox violation, but no image read either.

According to the docs, that property returns true "if the receiver uses the file scheme" (whatever that means). I guess folders too "use the file scheme". I switched to using hasDirectoryPath instead, as suggested in this other answer.

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
  • 1
    Re: URL "scheme" - it is the part of the URL before the colon which determines the kind of URL. Common schemes are http/https (internet resource path), mailto (email address) and file (any file system object in the file system the URL is evaluated on), etc. I.e. "file" is shorthand for "local file system object"; which includes files, folders/directories, pipes, devices; and not about files *per se*. HTH – CRD Mar 23 '19 at 23:10
  • @CRD Thanks, I was aware of URL schemes. All URLs I have encountered begin with `file:///...` (which I assume is `file://` plus another forward slash representing the root directory...?) – Nicolas Miari Mar 25 '19 at 06:28
  • 1
    It's not actually root but a separator, but thinking of it as root when the path is Unix-like is mainly harmless. See [File URI](https://en.m.wikipedia.org/wiki/File_URI_scheme) for the syntax. – CRD Mar 25 '19 at 08:14
  • @CRD I see, thanks again. I mean, the other schemes only have two slashes :-) – Nicolas Miari Mar 25 '19 at 08:15