swiftmacosnsopenpanelnssavepanel

Customizing sandboxed NSSavePanel alert


I am validating urls from NSSavePanel using the delegate's panel(_:validate) method, throwing error in case of invalid url. In such case the NSSavePanel presents an alert, which I want to customize (meaning present some human readable description) depending on the error thrown, keeping the save panel window open and then letting you choose another path.

LocalizedError works just fine when not using App Sandbox but in a sandboxed app the getter for error description is never called and the message in the alert is generic "Operation couldn't be completed. (#yourErrorType)", which I guess is somehow caused by the different inheritance chain for sandboxed NSSavePanels.

I am struggling figuring a way around this - is it possible to customize the alert somehow while still keeping the app sandboxed?

Addendum: Permissions for User Selected File => r/w. Running the following example produces different alerts with/without sandbox.

func runSavePanel()
{
    let panel = NSSavePanel()
    let delegate = SavePanelDelegate()
    panel.delegate = delegate
    _ = panel.runModal()
}

class SavePanelDelegate: NSObject, NSOpenSavePanelDelegate {
    func panel(_ sender: Any, validate url: URL) throws {
        throw CustomError.whatever
    }
}

enum CustomError: LocalizedError {
    case whatever

    var errorDescription: String? {
        get {
            return "my description"
        }
    }
}

Solution

  • So, after a bit of further digging I can tell the solution of the riddle finally although I can only guess the reasons why it was made tricky by Apple. Apparently NSError exclusively needs to be used. The customization has to be done in userInfo, say

    let userInfo = [NSLocalizedDescriptionKey: "yourLocalizedDescription", NSLocalizedRecoverySuggestionErrorKey: "yourSuggestion"]
    throw NSError(domain: "whatever", code: 0, userInfo: userInfo)
    

    etc. By the way subclassing NSError doesn't work, the Sandbox will just happily ignore you :)