How to move a file in swift - swift

I'm trying to move a file to documents directory after I choose the file by using UIDocumentPickerViewController.
I don't see any error, but the file doesn't move to the directory.
I'd like to know how can I move the file.
class MyClassController: UIViewController,UIDocumentPickerDelegate {
var thisURL:URL?
#IBAction func add(_ sender: Any) {
let add = UIAlertAction(title: "add", style: .default, handler: {(alert: UIAlertAction!) in
do {
let myURL = URL(string:"\(self.thisURL!)")!
let path = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let MyDesPath = path.appendingPathComponent(myURL.lastPathComponent)
print(path)
do {
if FileManager.default.fileExists(atPath: “\(MyDesPath)") == false {
try FileManager.default.moveItem(at: myURL, to: URL(string:”\(MyDesPath)")!)
}
else {
}
}
catch let error {
print(error)
}
}
catch let error{
print(error)
return
}
})
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
NSLog("documentPicker executed")
thisURL = urls[0]
self.fileName.text = "\(thisURL!.lastPathComponent)"
}
}

I have done the same thing earlier, Please refer below steps and code:
Create a file URL to the temporary folder
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
Append filename and extension to URL
tempURL.appendPathComponent(url.lastPathComponent)
If the file with the same name exists remove it (replace a file with the new one)
Full Code:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let newUrls = urls.flatMap { (url: URL) -> URL? in
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
tempURL.appendPathComponent(url.lastPathComponent)
do {
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
try FileManager.default.moveItem(atPath: url.path, toPath: tempURL.path)
return tempURL
} catch {
print(error.localizedDescription)
return nil
}
}
}

Full credit to #COVID19 for the answer though in my case using UIDocumentPickerViewController put the file in the temp folder already and some may like to place it in the documents directory. This is a way of going about that with a single file being selected.
guard let url = urls.first else { return }
var newURL = FileManager.getDocumentsDirectory()
newURL.appendPathComponent(url.lastPathComponent)
do {
if FileManager.default.fileExists(atPath: newURL.path) {
try FileManager.default.removeItem(atPath: newURL.path)
}
try FileManager.default.moveItem(atPath: url.path, toPath: newURL.path)
print("The new URL: \(newURL)")
} catch {
print(error.localizedDescription)
}
and my handy helper method
extension FileManager {
static func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
}

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?

I am trying to delete a pin on map, stored in the documents directory, but I get this error The file “SavedLocations” couldn’t be opened

I am trying to delete a pin on map, I have several of them on the map stored in the documents directory.I want to delete only a single pin at time, but once I call my delete method, I get this error- The file “SavedLocations” couldn’t be opened. How do I delete a single pin saved in the documents directory? Please help, Thank you.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
//Load method
func loadData() {
print("loaded data")
let filename = getDocumentsDirectory().appendingPathComponent("SavedLocations")
do {
let data = try Data(contentsOf: filename)
locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data)
} catch {
print("Unable to load saved data")
}
}
//Save method
func saveData() {
print("saved data")
do {
let filename = getDocumentsDirectory().appendingPathComponent("SavedLocations")
// print(filename)
let data = try JSONEncoder().encode(self.locations)
try data.write(to: filename, options: [.atomicWrite, .completeFileProtection])
} catch {
print("Unable to save data")
}
}
//Delete method
func delete() {
let fileManager = FileManager.default
let filename = getDocumentsDirectory().appendingPathComponent("SavedLocations")
print("Pin deleted")
print(filename)
do {
let items = try fileManager.contentsOfDirectory(at: filename, includingPropertiesForKeys: .none, options: .skipsHiddenFiles)
print(items)
for item in items {
try fileManager.removeItem(at: item)
print(item)
}
} catch let error {
print("\(error.localizedDescription)")
}
}
Change this method to:
func delete() {
let fileManager = FileManager.default
let filename = getDocumentsDirectory().appendingPathComponent("SavedLocations")
print("Pin deleted")
print(filename)
do {
let items = try fileManager.contentsOfDirectory(at: filename, includingPropertiesForKeys: .none, options: .skipsHiddenFiles)
print(items)
if (newArray.count > 0){
try fileManager.removeItem(at: items[0])
}
} catch let error {
print("\(error.localizedDescription)")
}
}

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

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)
}
}
}

Swift 3: Reading content of folder? [duplicate]

What is wrong with my code for getting the filenames in the document folder?
func listFilesFromDocumentsFolder() -> [NSString]?{
var theError = NSErrorPointer()
let dirs = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if dirs != nil {
let dir = dirs![0] as NSString
let fileList = NSFileManager.defaultManager().contentsOfDirectoryAtPath(dir, error: theError) as [NSString]
return fileList
}else{
return nil
}
}
I thought I read the documents correctly and I am very sure about what is in the documents folder, but "fileList" does not show anything? "dir" shows the path to the folder.
Swift 5
do {
// Get the document directory url
let documentDirectory = try FileManager.default.url(
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
print("documentDirectory", documentDirectory.path)
// Get the directory contents urls (including subfolders urls)
let directoryContents = try FileManager.default.contentsOfDirectory(
at: documentDirectory,
includingPropertiesForKeys: nil
)
print("directoryContents:", directoryContents.map { $0.localizedName ?? $0.lastPathComponent })
for url in directoryContents {
print(url.localizedName ?? url.lastPathComponent)
}
// if you would like to hide the file extension
for var url in directoryContents {
url.hasHiddenExtension = true
}
for url in directoryContents {
print(url.localizedName ?? url.lastPathComponent)
}
// if you want to get all mp3 files located at the documents directory:
let mp3s = directoryContents.filter(\.isMP3).map { $0.localizedName ?? $0.lastPathComponent }
print("mp3s:", mp3s)
} catch {
print(error)
}
You would need to add those extensions to your project
extension URL {
var typeIdentifier: String? { (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier }
var isMP3: Bool { typeIdentifier == "public.mp3" }
var localizedName: String? { (try? resourceValues(forKeys: [.localizedNameKey]))?.localizedName }
var hasHiddenExtension: Bool {
get { (try? resourceValues(forKeys: [.hasHiddenExtensionKey]))?.hasHiddenExtension == true }
set {
var resourceValues = URLResourceValues()
resourceValues.hasHiddenExtension = newValue
try? setResourceValues(resourceValues)
}
}
}
This solution works with Swift 4 (Xcode 9.2) and also with Swift 5 (Xcode 10.2.1+):
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
do {
let fileURLs = try fileManager.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil)
// process files
} catch {
print("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)")
}
Here's a reusable FileManager extension that also lets you skip or include hidden files in the results:
import Foundation
extension FileManager {
func urls(for directory: FileManager.SearchPathDirectory, skipsHiddenFiles: Bool = true ) -> [URL]? {
let documentsURL = urls(for: directory, in: .userDomainMask)[0]
let fileURLs = try? contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil, options: skipsHiddenFiles ? .skipsHiddenFiles : [] )
return fileURLs
}
}
// Usage
print(FileManager.default.urls(for: .documentDirectory) ?? "none")
A shorter syntax for SWIFT 3
func listFilesFromDocumentsFolder() -> [String]?
{
let fileMngr = FileManager.default;
// Full path to documents directory
let docs = fileMngr.urls(for: .documentDirectory, in: .userDomainMask)[0].path
// List all contents of directory and return as [String] OR nil if failed
return try? fileMngr.contentsOfDirectory(atPath:docs)
}
Usage example:
override func viewDidLoad()
{
print(listFilesFromDocumentsFolder())
}
Tested on xCode 8.2.3 for iPhone 7 with iOS 10.2 & iPad with iOS 9.3
Apple states about NSSearchPathForDirectoriesInDomains(_:_:_:):
You should consider using the FileManager methods urls(for:in:) and url(for:in:appropriateFor:create:) which return URLs, which are the preferred format.
With Swift 5, FileManager has a method called contentsOfDirectory(at:includingPropertiesForKeys:options:). contentsOfDirectory(at:includingPropertiesForKeys:options:) has the following declaration:
Performs a shallow search of the specified directory and returns URLs for the contained items.
func contentsOfDirectory(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: FileManager.DirectoryEnumerationOptions = []) throws -> [URL]
Therefore, in order to retrieve the urls of the files contained in documents directory, you can use the following code snippet that uses FileManager's urls(for:in:) and contentsOfDirectory(at:includingPropertiesForKeys:options:) methods:
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
do {
let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil, options: [])
// Print the urls of the files contained in the documents directory
print(directoryContents)
} catch {
print("Could not search for urls of files in documents directory: \(error)")
}
As an example, the UIViewController implementation below shows how to save a file from app bundle to documents directory and how to get the urls of the files saved in documents directory:
import UIKit
class ViewController: UIViewController {
#IBAction func copyFile(_ sender: UIButton) {
// Get file url
guard let fileUrl = Bundle.main.url(forResource: "Movie", withExtension: "mov") else { return }
// Create a destination url in document directory for file
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let documentDirectoryFileUrl = documentsDirectory.appendingPathComponent("Movie.mov")
// Copy file to document directory
if !FileManager.default.fileExists(atPath: documentDirectoryFileUrl.path) {
do {
try FileManager.default.copyItem(at: fileUrl, to: documentDirectoryFileUrl)
print("Copy item succeeded")
} catch {
print("Could not copy file: \(error)")
}
}
}
#IBAction func displayUrls(_ sender: UIButton) {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
do {
let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil, options: [])
// Print the urls of the files contained in the documents directory
print(directoryContents) // may print [] or [file:///private/var/mobile/Containers/Data/Application/.../Documents/Movie.mov]
} catch {
print("Could not search for urls of files in documents directory: \(error)")
}
}
}
Simple and dynamic solution (Swift 5):
extension FileManager {
class func directoryUrl() -> URL? {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths.first
}
class func allRecordedData() -> [URL]? {
if let documentsUrl = FileManager.directoryUrl() {
do {
let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil)
return directoryContents.filter{ $0.pathExtension == "m4a" }
} catch {
return nil
}
}
return nil
}}
This code prints out all the directories and files in my documents directory:
Some modification of your function:
func listFilesFromDocumentsFolder() -> [String]
{
let dirs = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs != [] {
let dir = dirs[0]
let fileList = try! FileManager.default.contentsOfDirectory(atPath: dir)
return fileList
}else{
let fileList = [""]
return fileList
}
}
Which gets called by:
let fileManager:FileManager = FileManager.default
let fileList = listFilesFromDocumentsFolder()
let count = fileList.count
for i in 0..<count
{
if fileManager.fileExists(atPath: fileList[i]) != true
{
print("File is \(fileList[i])")
}
}
Swift 2.0 Compability
func listWithFilter () {
let fileManager = NSFileManager.defaultManager()
// We need just to get the documents folder url
let documentsUrl = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as NSURL
do {
// if you want to filter the directory contents you can do like this:
if let directoryUrls = try? NSFileManager.defaultManager().contentsOfDirectoryAtURL(documentsUrl, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants) {
print(directoryUrls)
........
}
}
}
OR
func listFiles() -> [String] {
var theError = NSErrorPointer()
let dirs = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if dirs != nil {
let dir = dirs![0]
do {
let fileList = try NSFileManager.defaultManager().contentsOfDirectoryAtPath(dir)
return fileList as [String]
}catch {
}
}else{
let fileList = [""]
return fileList
}
let fileList = [""]
return fileList
}

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
}