swiftmacoscocoansbundlenssavepanel

Save/Copy a file from Bundle to Desktop using NSSavePanel


I’m creating a macOS app which ships with some .zip files within its Bundle directory.

Users should be able to save these files from my app to a custom directory.

I found NSSavePanel and thought it is the right approach — that’s what I have so far:

@IBAction func buttonSaveFiles(_ sender: Any) {

    let savePanel = NSSavePanel()

    let bundleFile = Bundle.main.resourcePath!.appending("/MyCustom.zip")

    let targetPath = NSHomeDirectory()
    savePanel.directoryURL = URL(fileURLWithPath: targetPath.appending("/Desktop")) 
    // Is appeding 'Desktop' a good solution in terms of localisation?

    savePanel.message = "My custom message."
    savePanel.nameFieldStringValue = "MyFile"
    savePanel.showsHiddenFiles = false
    savePanel.showsTagField = false
    savePanel.canCreateDirectories = true
    savePanel.allowsOtherFileTypes = false
    savePanel.isExtensionHidden = true

    savePanel.beginSheetModal(for: self.view.window!, completionHandler: {_ in })

}

I couldn’t find out how to 'hand over' the bundleFile to the savePanel.

So my main question is: How can I save/copy a file from the app bundle to a custom directory?

Additional questions depending NSSavePanel: 1) It seems that it’s not localized by default (my Xcode scheme is set to German, but the panel appears in English), do I have to customize that by myself? 2) Is there a way to present the panel expanded by default?


Solution

  • You should use Bundle.main.url to get your existing file URL, then get the destination URL with the panel, then copy the file. The panel doesn't do anything to files, it just gets their URL.

    Example:

    // the panel is automatically displayed in the user's language if your project is localized
    let savePanel = NSSavePanel()
    
    let bundleFile = Bundle.main.url(forResource: "MyCustom", withExtension: "zip")!
    
    // this is a preferred method to get the desktop URL
    savePanel.directoryURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!
    
    savePanel.message = "My custom message."
    savePanel.nameFieldStringValue = "MyFile"
    savePanel.showsHiddenFiles = false
    savePanel.showsTagField = false
    savePanel.canCreateDirectories = true
    savePanel.allowsOtherFileTypes = false
    savePanel.isExtensionHidden = true
    
    if let url = savePanel.url, savePanel.runModal() == NSFileHandlingPanelOKButton {
        print("Now copying", bundleFile.path, "to", url.path)
        // Do the actual copy:
        do {
            try FileManager().copyItem(at: bundleFile, to: url)
        } catch {
            print(error.localizedDescription)
        }
    } else {
        print("canceled")
    }
    

    Also, note that the panel being expanded or not is a user selection, you can't force it from your app.