Copy and Save File from Application Bundle to Desktop / somewhere else - swift

I want to save a MIDI-file to a certain folder. But unfortunately just get an "Untitled" txt file.
I found this code which I tried:
let savePanel = NSSavePanel()
let bundleFile = Bundle.main.url(forResource: "Melody", withExtension: "mid")!
// 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 = false
if let url = savePanel.url, savePanel.runModal() == NSApplication.ModalResponse.OK {
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")
}
What can I improve to copy the MIDI-File from the Application Bundle to the e.g. Desktop??
Thanks!

Looking over some old code I wrote to copy a file from the app bundle to a location on someone's Mac, I had to append the name of the file as an additional path component to the destination URL to get the file to copy properly. Using your code example, the code would look similar to the following:
let name = "Melody.mid"
// url is the URL the user chose from the Save panel.
destinationURL = url.appendingPathComponent(name)
// Use destinationURL instead of url as the to: argument in FileManager.copyItem.

Related

Swift - Zip Files from directory without creating a directory inside zip directory

I'm working with Swift 5.0 and I want to zip 3 files without installing additional dependencies.
Currently I'm able to zip these 3 Files, which all are in the same "root" directory. But if I open the created and exported zip-file (final.zip), than I get the folder (root) in which the 3 files were saved. I don't want this "root" folder. I want to see my data directly inside the final.zip
Below is my code. Can someone help me out?
let fm = FileManager.default
let baseDirectoryUrl = fm.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("root", isDirectory: true)
if !manager.fileExists(atPath: baseDirectoryUrl.relativePath) {
try! manager.createDirectory(
at: baseDirectoryUrl,
withIntermediateDirectories: true,
attributes: nil
)
}
// path of data
let data1_path = baseDirectoryUrl.appendingPathComponent("data1.vec3")
let data2_path = baseDirectoryUrl.appendingPathComponent("data2.vec3")
let data3_path = baseDirectoryUrl.appendingPathComponent("data3.json")
// write wData1 to data1_path
try! wData1.write(to: data1_path)
// write wData2 to data2_path
try! wData2.write(to: data2_path)
// write wData3 to data3_path
try! wData3.write(to: data3_path)
// this will hold the URL of the zip file
var archiveUrl: URL?
// if we encounter an error, store it here
var error: NSError?
let coordinator = NSFileCoordinator()
// zip up the root directory
// this method is synchronous and the block will be executed before it returns
// if the method fails, the block will not be executed though
// if you expect the archiving process to take long, execute it on another queue
coordinator.coordinate(readingItemAt: baseDirectoryUrl, options: [.forUploading], error: &error) { (zipUrl) in
// zipUrl points to the zip file created by the coordinator
// zipUrl is valid only until the end of this block, so we move the file to a temporary folder
let tmpUrl = try! fm.url(
for: .itemReplacementDirectory,
in: .userDomainMask,
appropriateFor: zipUrl,
create: true
).appendingPathComponent("export.zip", isDirectory: false)
try! fm.copyItem(at: zipUrl, to: tmpUrl)
// store the URL so we can use it outside the block
archiveUrl = tmpUrl
}
if let archiveUrl = archiveUrl {
// bring up the share sheet so we can send the archive with AirDrop for example
let avc = UIActivityViewController(activityItems: [archiveUrl], applicationActivities: nil)
avc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
present(avc, animated: true)
} else {
print(error)
}
You can use this zip package in your app with shouldKeepParent: false
https://github.com/weichsel/ZIPFoundation#in-memory-archives

swift save file after download Alamofire

download and save file
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
// var fileURL = self.createFolder(folderName: downloadFolderName)
var fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileName = URL(string : currentFile.link )
fileURL = fileURL.appendingPathComponent((fileName?.lastPathComponent)!)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(currentDownloadedFile.link , to: destination).response(completionHandler: { (DefaultDownloadResponse) in
print("res ",DefaultDownloadResponse.destinationURL!);
completion(true)
})
but when i wont to check file in this dirrectory i get nil
let filemanager:FileManager = FileManager()
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let files = filemanager.enumerator(atPath: fileURL.absoluteString) // = nil
while let file = files?.nextObject() {
print(file)
}
if i save local path to file and after reload app wont to share it -> "share" app cant send file (mb cant found it)
can u pls help me. how it works ? why when i print all files he didnt find it? how to save file who after reboot app it will be saved in same link
You are using the wrong API
For file system URLs use always path, absoluteString returns the full string including the scheme (e. g. file:// or http://)
let files = filemanager.enumerator(atPath: fileURL.path)

Get Full File Path During Enumeration

I'm using the following code to enumerate a user selected path and populate an array with files names with specific extensions.
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: path)!
while let element = enumerator.nextObject() as? String {
if ((element.hasSuffix("jpg"))||element.hasSuffix("playground")) {
if(!self.yourArray.contains(element))
{
self.yourArray.append(element)
}
}
I need to get the complete file path to add the file to a processing queue.
Also how I can prevent the enumerator from exploring sub directories.
Please advice.
First of all use the URL related API, it's more versatile.
Unlike the DirectoryEnumerator contentsOfDirectory returns the URLs of all items in the directory without the subdirectories.
An easy solution is an array of path extensions and the filter function
let url = URL(fileURLWithPath: "/Users/myUser/Pictures/")
let filterExtensions = ["jpg", "playground"]
do {
self.yourArray = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
.filter{ filterExtensions.contains($0.pathExtension) }
print(yourArray)
} catch {
print(error)
}
Be aware that yourArray will contain URLs, not string paths.
To use a directory enumerator:
This will allow you to get all the files in all subdirectories, not just the one level deep. Turn the path that you provided to fileManager.enumerator(atPath: path)! into a URL like below and then append the path provided by directory enumerator to this URL:
let directoryUrl = URL(fileURLWithPath: path)
let pathUrl = directoryUrl.appendingPathComponent(element)
If I was writing your code, I would also do some renames/ improvements:
func main() {
// Set up your variables
let fileManager = FileManager.default
let path = "/Users/username"
var files = [URL]() // Using a URL instead of a path.
let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: path)!
let directoryUrl = URL(fileURLWithPath: path)
while let filepath = enumerator.nextObject() as? String {
if ((filepath.hasSuffix(".jpg")) || filepath.hasSuffix(".playground")) {
let pathUrl = directoryUrl.appendingPathComponent(filepath)
files.append(pathUrl)
}
}
}

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?
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.

Replace folder in file system using Swift

How can I copy and paste the entire content of a folder using Swift on a OS X? If the destinationPath already contains the folder, than it should replace it.
I tried
let appSupportSourceURL = NSURL(string: appSupportSourcePath)
let appSupportDestinationURL = NSURL(string: appSupportDestinationPath+"/"+appSupportFileName)
if (fileManager.isReadableFileAtPath(appSupportSourcePath)){
do {
try fileManager.copyItemAtURL(appSupportSourceURL!, toURL: appSupportDestinationURL!)}
catch{
}
}
but I realised, that this only works for files. I am trying to replace a whole folder.
I know Apple encourages new code to use the URLs to specify file system path. However NSFileManager is an old class and it's still in transition between the old string-based path and the new URL-based paradigm. Try this:
let appSupportSourcePath = "..."
let appSupportDestinationPath = "..."
let fileManager = NSFileManager.defaultManager()
do {
// Delete if already exists
if fileManager.fileExistsAtPath(appSupportDestinationPath) {
try fileManager.removeItemAtPath(appSupportDestinationPath)
}
try fileManager.copyItemAtPath(appSupportSourcePath, toPath: appSupportDestinationPath)
} catch {
print(error)
}
Edit: method with NSURL
let appSupportSourceURL = NSURL(fileURLWithPath: "...", isDirectory: true)
let appSupportDestionURL = NSURL(fileURLWithPath: "...", isDirectory: true)
try! NSFileManager.defaultManager().copyItemAtURL(appSupportSourceURL, toURL: appSupportDestionURL)