Is it possible to load images recovered from a server in runtime into a plane object? - swift

I have been asked to build an app that shows a catalog with AR, so what I need to do is pretty simple: when an user chooses a product I must load the image recovered in base64 from the server into a plane object. Is this possible with swift - arkit ? Or are all the sprites/images/textures required to be previously loaded into the assets folder?

You can definitely download resources from a server, save them to the device (e.g in NSDocumentsDirectory), and then load with the file URL. I do it for a similar use case as yours -at least it sounds so, per the description you gave-
EDIT
Here's the relevant code. I use Alamofire to download from the server and ZIPFoundation for unzipping. I believe that if you just need to download an image, it'll be a bit simpler, probably not needing the unzip part.
let modelsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
func loadNodeWithID(_ id: String, completion: #escaping (SCNNode?) -> Void) {
// Check that assets for that model are not already downloaded
let fileManager = FileManager.default
let dirForModel = modelsDirectory.appendingPathComponent(id)
let dirExists = fileManager.fileExists(atPath: dirForModel.path)
if dirExists {
completion(loadNodeWithIdFromDisk(id))
} else {
let dumbURL = "http://yourserver/yourfile.zip"
downloadZip(from: dumbURL, at: id) {
if let url = $0 {
print("Downloaded and unzipped at: \(url.absoluteString)")
completion(self.loadNodeWithIdFromDisk(id))
} else {
print("Something went wrong!")
completion(nil)
}
}
}
}
func loadNodeWithIdFromDisk(_ id: String) -> SCNNode? {
let fileManager = FileManager.default
let dirForModel = modelsDirectory.appendingPathComponent(id)
do {
let files = try fileManager.contentsOfDirectory(atPath: dirForModel.path)
if let objFile = files.first(where: { $0.hasSuffix(".obj") }) {
let objScene = try? SCNScene(url: dirForModel.appendingPathComponent(objFile), options: nil)
let objNode = objScene?.rootNode.firstChild()
return objNode
} else {
print("No obj file in directory: \(dirForModel.path)")
return nil
}
} catch {
print("Could not enumarate files or load scene: \(error)")
return nil
}
}
func downloadZip(from urlString: String, at destFileName: String, completion: ((URL?) -> Void)?) {
print("Downloading \(urlString)")
let fullDestName = destFileName + ".zip"
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let fileURL = modelsDirectory.appendingPathComponent(fullDestName)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, to: destination).response { response in
let error = response.error
if error == nil {
if let filePath = response.destinationURL?.path {
let nStr = NSString(string: filePath)
let id = NSString(string: nStr.lastPathComponent).deletingPathExtension
print(response)
print("file downloaded at: \(filePath)")
let fileManager = FileManager()
let sourceURL = URL(fileURLWithPath: filePath)
var destinationURL = modelsDirectory
destinationURL.appendPathComponent(id)
do {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: sourceURL, to: destinationURL)
completion?(destinationURL)
} catch {
completion?(nil)
print("Extraction of ZIP archive failed with error: \(error)")
}
} else {
completion?(nil)
print("File path not found")
}
} else {
// Handle error
completion?(nil)
}
}
}

Related

Cocoa: How do I change the download destination? Swift

I’m trying to download a file from a URL, I managed to do that however, the it will download to ~Libray directory. How do I change the directory to the downloads folder? or out of the library directory.
Here is my file downloader…
import Foundation
class FileDownloader {
static func loadFileSync(url: URL, completion: #escaping (String?, Error?) -> Void)
{
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .allDomainsMask).first!
let destinationUrl = documentsUrl.appendingPathComponent(url.lastPathComponent)
if FileManager().fileExists(atPath: destinationUrl.path)
{
print("File already exists [\(destinationUrl.path)]")
completion(destinationUrl.path, nil)
}
else if let dataFromURL = NSData(contentsOf: url)
{
if dataFromURL.write(to: destinationUrl, atomically: true)
{
print("file saved [\(destinationUrl.path)]")
completion(destinationUrl.path, nil)
}
else
{
print("error saving file")
let error = NSError(domain:"Error saving file", code:1001, userInfo:nil)
completion(destinationUrl.path, error)
}
}
else
{
let error = NSError(domain:"Error downloading file", code:1002, userInfo:nil)
completion(destinationUrl.path, error)
}
}
static func loadFileAsync(url: URL, completion: #escaping (String?, Error?) -> Void)
{
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationUrl = documentsUrl.appendingPathComponent(url.lastPathComponent)
if FileManager().fileExists(atPath: destinationUrl.path)
{
print("File already exists [\(destinationUrl.path)]")
completion(destinationUrl.path, nil)
}
else
{
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil)
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = session.dataTask(with: request, completionHandler:
{
data, response, error in
if error == nil
{
if let response = response as? HTTPURLResponse
{
if response.statusCode == 200
{
if let data = data
{
if let _ = try? data.write(to: destinationUrl, options: Data.WritingOptions.atomic)
{
completion(destinationUrl.path, error)
}
else
{
completion(destinationUrl.path, error)
}
}
else
{
completion(destinationUrl.path, error)
}
}
}
}
else
{
completion(destinationUrl.path, error)
}
})
task.resume()
}
}
}
I took a look at the code and I think it has to do something with this
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .allDomainsMask).first!
let destinationUrl = documentsUrl.appendingPathComponent(url.lastPathComponent)
I have tried chinging the code from documentDirectory to desktopDirectory but that still puts it in the library directory. How do I change it from the library directory to the downloads directory?
You are running a sandboxed app. The Appname/Data/... directories are aliases of the real directories.
Also, you should use userDomainMask instead of allDomainsMask to search for the path in user's home directory. The correct directory you are looking for is downloadsDirectory.
let downloadsUrl = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first!
let destinationUrl = downloadsUrl.appendingPathComponent(url.lastPathComponent)
There is another SO thread about sandboxed directories:
how to get /Users/username/Downloads path in a sandboxed app?

Could not open obj file

so I'm working on a project that pulls zip files which include .obj files, unzips them and displays them using ARkit. At the moment I've been able to unzip and save the file in my documents directory as a .obj file and it says the file exists, but when I try to convert it into a scn it says "Could not open OBJ file". Here is my code and I'm trying to figure out where I went wrong. The file size is 314 KB so I know that's not the issue and the obj file isn't corrupted because I can download it to my computer and open it up.
class ViewController: UIViewController {
var testData: Store?
var logoImage: UIImage?
var urlForObject: String?
var tempDirectoryString: String?
var tempDirectoryURL: URL?
var testModelData: Data?
var testData2: Data?
var testData3: Models?
var tempDocumentsURL: URL?
#IBOutlet weak var logoImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.clearCowZipFolder()
self.clearCowFolder()
self.requestZipFile(){ response in
self.testData2 = response
do{
let json = try? JSONSerialization.jsonObject(with: response, options: [])
if let array = json as? [String] {
if let firstObject = array.first {
//print(firstObject)
}
for object in array {
// access all objects in array
}
for case let string as String in array {
// access only string values in array
}
}
}catch{
print("errorrrr")
}
}
self.tempDocumentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = self.tempDocumentsURL!.appendingPathComponent("Man.zip")
var destPath = self.tempDocumentsURL!.appendingPathComponent("Man")
//Create URL to the source file you want to download
let fileURL = URL(string: "http://markitapi.com/stores/TestStore/models/Man")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response3, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response3 as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
do{
let unzipDirectory = try Zip.quickUnzipFile(destinationFileUrl) //Unzip
print("HHERHERHEHRE")
destPath = unzipDirectory
}catch{
print("error while unzipping")
}
print("UNZIPPED PATH")
//print(destPath)
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
if let pathComponent = url.appendingPathComponent("Man/Man.obj") {
let filePath = pathComponent.path
//print(filePath)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
} else {
print("FILE PATH NOT AVAILABLE")
}
let url2 = (String(describing: destPath) + "Man.obj")
let url3 = URL(fileURLWithPath: url2)
print(url3.pathExtension)
let asset = MDLAsset(url: url3)
//print(asset)
let object = asset.object(at: 0)
let node = SCNNode(mdlObject: object)
//print(node)
}
} else {
//print("Error took place while downloading a file. Error description: %#", error?.localizedDescription);
}
}
task.resume()
}
func clearCowZipFolder() {
let fileNameToDelete = "Man.zip"
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
//print("Local path = \(filePath)")
} else {
print("Could not find local directory to store file")
return
}
do {
let fileManager = FileManager.default
// Check if file exists
if fileManager.fileExists(atPath: filePath) {
// Delete file
try fileManager.removeItem(atPath: filePath)
} else {
print("File does not exist")
}
}
catch let error as NSError {
print("An error took place: \(error)")
}
}
func clearCowFolder() {
let fileNameToDelete = "Man"
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
//print("Local path = \(filePath)")
} else {
print("Could not find local directory to store file")
return
}
do {
let fileManager = FileManager.default
// Check if file exists
if fileManager.fileExists(atPath: filePath) {
// Delete file
try fileManager.removeItem(atPath: filePath)
} else {
print("File does not exist")
}
}
catch let error as NSError {
print("An error took place: \(error)")
}
}
func requestZipFile(success successBlock: #escaping (Data) -> Void){
Alamofire.request("http://markitapi.com/stores/TestStore/models").responseJSON { (response) in
do{
if(response.result.isSuccess){
successBlock(response.data!)
}
}catch{
print("error")
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The first part of the code checks to see if the file is available and it does print that statement, so I know the file is there.

Trying to clear document folder Swift

I use following method to add file (download) to document directory:
static func downloadFileWithLink(linkString : String){
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("downloadedFile")
//Create URL to the source file you want to download
let fileURL = URL(string: linkString)
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription);
}
}
task.resume()
}
It simply download file to document folder in sandbox. I looked for method that delete files in documents folder, and tried following:
static func deleteFiledInDocDirectory(){
let fileManager = FileManager.default
let tempFolderPath = NSTemporaryDirectory()
do {
let filePaths = try fileManager.contentsOfDirectory(atPath: tempFolderPath)
for filePath in filePaths {
try fileManager.removeItem(atPath: tempFolderPath + filePath)
}
} catch {
print("Could not clear temp folder: \(error)")
}
}
However, when i inspect sandbox, downloaded file is still here. How to delete it?
In deleteFiledInDocDirectory() you're using NSTemporaryDirectory instead of the documents directory that you originally saved the file to.
Change tempFolderPath to be set using following:
guard let tempFolderPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
return // documents directory not found for some reason
}
The full method:
static func deleteFiledInDocDirectory(){
guard let tempFolderPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first?.absoluteString else {
return // documents directory not found for some reason
}
let fileManager = FileManager.default
do {
let filePaths = try fileManager.contentsOfDirectory(atPath: tempFolderPath)
for filePath in filePaths {
try fileManager.removeItem(atPath: tempFolderPath + filePath)
}
} catch {
print("Could not clear temp folder: \(error)")
}
}

Setting Alamofire custom destination file name instead of using suggestedDownloadDestination

I got many lists of invoice file at my table view as well as many download buttons at each cell.When I clicked one of it,it will download the invoice file.But,the problem is the server response suggested file name is "invoice.pdf" at every file I downloaded.So,I need to edit the file name manually before I save to document after it was downloaded.So,how to edit the file name manually after it was download successfully and save it in document as temporaryurl without using Alamofire.Request.suggestedDownloadDestination.
Here is my download function.
func downloadInvoice(invoice: Invoice, completionHandler: (Double?, NSError?) -> Void) {
guard isInvoiceDownloaded(invoice) == false else {
completionHandler(1.0, nil) // already have it
return
}
let params = [
"AccessToken" : “xadijdiwjad12121”]
// Can’t use the destination file anymore because my server only return one file name “invoice.pdf” no matter which file i gonna download
// So I have to manually edit my file name which i saved after it was downloaded.
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
// So I have to save file name like that ““2016_04_02_car_invoice_10021.pdf” [Date_car_invoice_timestamp(Long).pdf]
// Please look comment on tableView code
Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders?.updateValue("application/pdf",forKey: "Content-Type")
Alamofire.download(.POST, invoice.url,parameters:params, destination: destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print(totalBytesRead)
dispatch_async(dispatch_get_main_queue()) {
let progress = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
completionHandler(progress, nil)
}
}
.responseString { response in
print(response.result.error)
completionHandler(nil, response.result.error)
}
}
Here is the table view which gonna check downloaded file and when it click,shown on open in feature.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let invoice = dataController.invoices?[indexPath.row] {
dataController.downloadInvoice(invoice) { progress, error in
// TODO: handle error
print(progress)
print(error)
if (progress < 1.0) {
if let cell = self.tableView.cellForRowAtIndexPath(indexPath), invoiceCell = cell as? InvoiceCell, progressValue = progress {
invoiceCell.progressBar.hidden = false
invoiceCell.progressBar.progress = Float(progressValue)
invoiceCell.setNeedsDisplay()
}
}
if (progress == 1.0) {
// Here where i gonna get the downloaded file name from my model.
// invoice.filename = (Assume “2016_04_02_car_invoice_10021”)
if let filename = invoice.filename{
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docs = paths[0]
let pathURL = NSURL(fileURLWithPath: docs, isDirectory: true)
let fileURL = NSURL(fileURLWithPath: filename, isDirectory: false, relativeToURL: pathURL)
self.docController = UIDocumentInteractionController(URL: fileURL)
self.docController?.delegate = self
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) {
self.docController?.presentOptionsMenuFromRect(cell.frame, inView: self.tableView, animated: true)
if let invoiceCell = cell as? InvoiceCell {
invoiceCell.accessoryType = .Checkmark
invoiceCell.setNeedsDisplay()
}
}
}
}
}
}
}
So,my question is simple.I just don't want to use that code
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
because it use response.suggestedfilename.And I want to save file name manually on selected table view cell data.Any Help?Please don't mind that I posted some code in my question because I want everyone to see it clearly.
Destination is of type (NSURL, NSHTTPURLResponse) -> NSURL. so you can do something like this
Alamofire.download(.POST, invoice.url,parameters:params, destination: { (url, response) -> NSURL in
let pathComponent = "yourfileName"
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let fileUrl = directoryURL.URLByAppendingPathComponent(pathComponent)
return fileUrl
})
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print(totalBytesRead)
dispatch_async(dispatch_get_main_queue()) {
let progress = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
completionHandler(progress, nil)
}
}
.responseString { response in
print(response.result.error)
completionHandler(nil, response.result.error)
}
}
Swift 3.0
in swift 3.0 it's DownloadFileDestination
Alamofire.download(url, method: .get, to: { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
return (filePathURL, [.removePreviousFile, .createIntermediateDirectories])
})
.downloadProgress(queue: utilityQueue) { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
if let data = response.result.value {
let image = UIImage(data: data)
}
}
for more go to the Alamofire

Download file from server using Swift

Hi I have a whole bunch of .mp3 files I want to use with NSFileManager and store in the documents folder. Is there a way I can download the .mp3 files online and then have it save to the documents folder? This is what I'm using for a local file.
let filemanager = NSFileManager.defaultManager()
let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
let destinationPath:NSString = documentsPath.stringByAppendingString("/Attention.mp3")
if (!filemanager.fileExistsAtPath(destinationPath)) {
var theError: NSError?
let fileForCopy = NSBundle.mainBundle().pathForResource("Attention",ofType:"mp3")
filemanager.copyItemAtPath(fileForCopy!,toPath:destinationPath, error: &theError)
if (theError == nil) {
println("The music files has been saved.")
} else {
println("Error")
}
} else {
println("The files already exist")
}
edit/update: Xcode 11.5 • Swift 5.2
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
let alarm = URL(string: "https://www.ringtonemobi.com/storage/upload/user_id_1/iphone-5-alarm-2016-08-21-01-49-25.mp3")!
do {
try alarm.download(to: .documentDirectory) { url, error in
guard let url = url else { return }
self.player = AVPlayer(url: url)
self.player.play()
}
} catch {
print(error)
}
}
}
import Foundation
extension URL {
func download(to directory: FileManager.SearchPathDirectory, using fileName: String? = nil, overwrite: Bool = false, completion: #escaping (URL?, Error?) -> Void) throws {
let directory = try FileManager.default.url(for: directory, in: .userDomainMask, appropriateFor: nil, create: true)
let destination: URL
if let fileName = fileName {
destination = directory
.appendingPathComponent(fileName)
.appendingPathExtension(self.pathExtension)
} else {
destination = directory
.appendingPathComponent(lastPathComponent)
}
if !overwrite, FileManager.default.fileExists(atPath: destination.path) {
completion(destination, nil)
return
}
URLSession.shared.downloadTask(with: self) { location, _, error in
guard let location = location else {
completion(nil, error)
return
}
do {
if overwrite, FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
try FileManager.default.moveItem(at: location, to: destination)
completion(destination, nil)
} catch {
print(error)
}
}.resume()
}
}
Original answer
Xcode 8.3.2 • Swift 3.1
if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(audioUrl.lastPathComponent)
print(destination)
// check if it exists before downloading it
if FileManager.default.fileExists(atPath: destination.path) {
print("The file already exists at path")
} else {
// if the file doesn't exist
// just download the data from your url
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("audio"),
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: destination)
print("file saved")
} catch {
print(error)
}
}).resume()
}
}
Xcode 10.1, Swift 4
I used the example above from #leo-dabus but broke up the code a bit into two functions. One flaw I found in that approach was that it did not handle the case where the file is already downloaded.
This example will remove any previous file that was already downloaded and write the latest version.
/// Downloads a file asynchronously
func loadFileAsync(url: URL, completion: #escaping (Bool) -> Void) {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(url.lastPathComponent)
log.info(m: "downloading file from URL: \(url.absoluteString)")
if FileManager().fileExists(atPath: destination.path) {
print("The file already exists at path, deleting and replacing with latest")
if FileManager().isDeletableFile(atPath: destination.path){
do{
try FileManager().removeItem(at: destination)
print("previous file deleted")
self.saveFile(url: url, destination: destination) { (complete) in
if complete{
completion(true)
}else{
completion(false)
}
}
}catch{
print("current file could not be deleted")
}
}
// download the data from your url
}else{
self.saveFile(url: url, destination: destination) { (complete) in
if complete{
completion(true)
}else{
completion(false)
}
}
}
}
func saveFile(url: URL, destination: URL, completion: #escaping (Bool) -> Void){
URLSession.shared.downloadTask(with: url, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let location = location, error == nil
else { print("error with the url response"); completion(false); return}
do {
try FileManager.default.moveItem(at: location, to: destination)
print("new file saved")
completion(true)
} catch {
print("file could not be saved: \(error)")
completion(false)
}
}).resume()
}
I found the #leo-dabus worked straight away, but had to make two minor changes for my needs. This might be helpful for others.
Change #1: Handle filenames that come included with a path-extension
if let fileName = fileName {
if fileName.hasSuffix(self.pathExtension) {
destination = directory
.appendingPathComponent(fileName)
} else {
destination = directory
.appendingPathComponent(fileName)
.appendingPathExtension(self.pathExtension)
}
} else {
destination = directory
.appendingPathComponent(lastPathComponent)
}
Change #2: If the destination file exists, generate a unique name
E.g. generate File (2).txt to avoid overwriting File.txt, like a web browser would.
if !overwrite {
let pathExtension = destination.pathExtension
let lastComponent = destination.deletingPathExtension().lastPathComponent
var copyNumber = 2
var attemptedURL = destination
while FileManager.default.fileExists(atPath: attemptedURL.path) {
attemptedURL = destination
.deletingPathExtension()
.deletingLastPathComponent()
.appendingPathComponent("\(lastComponent) (\(copyNumber))")
.appendingPathExtension(pathExtension)
copyNumber += 1
}
destination = attemptedURL
}