NSOpenPanel not closing after OK button

I launch the following runSavePanel() from, e.g.,

@IBAction func JSONall(_ sender: NSButton) {
        if !nsPanelActivatedThisSessionForSandbox { 
        nsPanelActivatedThisSessionForSandbox = true

runSavePanel() }
...
}

where var nsPanelActivatedThisSessionForSandbox = false is declared as a global in the AppDelegate.swift file and is intended to prevent repeated user prompting as the program creates many files but needs sandbox acknowledgment at least once.

The problem behavior is that upon clicking the OK button, the app merrily creates the files in the chosen directory but the modal window stays open until the app is done creating files (around ten minutes). Here is my runSavePanel() and its (empty) delegate:

    func runSavePanel() {

        let defaults = UserDefaults.standard

        let savePanel = NSSavePanel()

        savePanel.canCreateDirectories     = true

        savePanel.canSelectHiddenExtension = false

        savePanel.showsHiddenFiles         = false

        //        savePanel.allowedFileTypes         = ["JSON", "csv"]                            //  deprecated

        savePanel.directoryURL             = defaults.url(forKey: "fileDirectoryPref")

        let delegate = OpenSavePanelDelegate()      //  cannot consolidate with next line

        savePanel.delegate = delegate

        let result = savePanel.runModal()

        if result == NSApplication.ModalResponse.OK {

            if savePanel.url != nil {

                do {

                    try savePanel.delegate!.panel!(savePanel, validate: savePanel.url!)

                    defaults.set(savePanel.url, forKey: "fileDirectoryPref")

                    //        defaults.set(savePanel.url, forKey: "NSNavLastRootDirectory")   //  does not write

                } catch {

                    print("\(result) ViewController Structures.swift:739 runSavePanel()")

                }

            }

        }

    }

    

    class OpenSavePanelDelegate: NSObject, NSOpenSavePanelDelegate {

        func panel(_ sender: Any, validate url: URL) throws {

            /*  In Swift, this method returns Void and is marked with the throws keyword to indicate that it throws an error in cases of failure.

             You call this method in a try expression and handle any errors in the catch clauses of a do statement, as described in Error Handling in The Swift Programming Language and About Imported Cocoa Error Parameters.

             */

//            throw CustomError.InvalidSelection

        }

    }

    

//    enum CustomError: LocalizedError {

//        case InvalidSelection

//

//        var errorDescription: String? {

//            get {

//                return "\(CustomError.InvalidSelection)"

//            }

//        }

//    }

Idk how to stop the double spacing when pasting my code. Sorry.

The delegate is a requirement but I don't know how to configure it. The app doesn't generate errors... or at least it allows the desired output.

Is the delegate supposed to terminate the modal window somehow?

how to stop the double spacing when pasting my code

This is easy: use the menu Paste and Match Style.

You do not show much code, so hard to be sure.

But you should replace

        let delegate = OpenSavePanelDelegate()      //  cannot consolidate with next line
        savePanel.delegate = delegate

by

        // -->>> REMOVE THIS LINE:  let delegate = OpenSavePanelDelegate()      //  cannot consolidate with next line
        savePanel.delegate = self // The class in which this code is

And declare that your class (a viewController probably) conforms to NSOpenSavePanelDelegate

Claude31, if I understand you, I protocol my ViewController, of which runSavePanel() and OpenSavePanelDelegate are a part. OK, did that. Also replaced the two lines with the one. Now the modal window goes away but the app no longer saves files to the chosen location, nor anywhere. As if the sandbox nix'ed it.

What & how do I catch any error in the delegate? The only sample code I found uses a custom error of my own choosing, not a system error.

Could you show the complete code of the class ? Thanks to Paste to avoid extra blank lines.

To save, code structure is as follows:

        let savePanel = NSSavePanel()
        savePanel.title ="Create file"
        savePanel.prompt = "Create"
        savePanel.allowedFileTypes = ["xxxx"]   // what you need
        let fileManager = FileManager.default
        
        savePanel.begin() { (result) -> Void in
            if result == NSApplication.ModalResponse.OK {
                let saveURL = savePanel.url! 
                self.saveToFile(data: data, on: saveURL)   // data defined elsewhere
            }
        }

What I do when I need to access a new directory, to manage sandboxing :

  • call NSOpen to select the directory where to save
  • save bookmarks
  • then call savePanel inside the completionHandler of the openPanel.

I admit, using sandbox may be nightmarish.

Claude31: the ViewController class is HUGE so, impractical to post.

"Developer Documentation" in Xcode is spartan. Having said that, it now shows ".allowedFileTypes[]" as deprecated but has: "var allowedContentTypes: [UTType]" and "var allowsOtherFileTypes: Bool" available.

In any case I'm OK with not specifying the fileType as I control them programmatically anyway.

btw if I use "savePanel.delegate = self" , the debugger posts: "unrecognized selector sent to instance" but if I use:

    let delegate = OpenSavePanelDelegate() 
    savePanel.delegate = delegate

it posts no errors but I'm back to my original problem; modal staying open until all 184 files written. |-(

I've triple-checked, runSavePanel() is only called once; when the JSONall() button is pushed. The modal must be waiting for some message-of-completion. I would have thought it receives the message when the OK button is hit or better yet, when the url is verified. Which all sounds like delegate action to me, but why is the delegate so lazy?

Why do you need to set a delegate ?

There seems to be a limited number of delegate functions. Which one do you use ?

func panel(Any, userEnteredFilename: String, confirmed: Bool) -> String?

Tells the delegate that the user confirmed a filename choice by clicking Save in a Save panel.

func panelSelectionDidChange(Any?)

Tells the delegate that the user changed the selection in the specified Save panel.

func panel(Any, didChangeToDirectoryURL: URL?)

Tells the delegate that the user changed the selected directory to the directory located at the specified URL.

func panel(Any, willExpand: Bool)

Tells the delegate that the Save panel is about to expand or collapse because the user clicked the disclosure triangle that displays or hides the file browser.

func panel(Any, shouldEnable: URL) -> Bool

Asks the delegate whether the specified URL should be enabled in the Open panel.

func panel(Any, validate: URL)

Asks the delegate to validate the URL for a file that the user selected.

All my code w.r.t. panel() and delegation is posted above. I set a delegate to the savePanel because I use the validate function. If the modal response is OK then it tries to save to the validated url.
There must be some unpublished delegation. Normally, a user would save one file, and this happens quickly. I initiate the savePanel just to get the sandbox satisfied and then calculate and save much data in many files. It is as if the delegate is waiting for control to be given back to the user. I do use DispatchQueues, a few for each file generated so there is no "freeze-out" of the user. Even so, I would think the panel should close after the first file is saved.

I am also confused by the NSOpenSavePanelDelegate functions. As a protocol I must declare a class that conforms. That class should already have those functions available which are they inherited by the instance variable. In practice, the class must re-declare those functions?!.
It may be obvious but I have avoided delegation until now. High level languages are powerful but when details of what a command does are not robust, results can be unexpected and potentially unamendable.

I do not catch the overall structure of your class.

Where is func runSavePanel() ? In which class ?

NSOpenPanel not closing after OK button
 
 
Q