Given, a file of path [[self documentsDirectory] stringByAppendingPathComponent:#"myFiles/aFile.txt"], How do I delete a file in my apps documents directory?
NSFileManager is a very handy tool:
[[NSFileManager defaultManager] removeItemAtPath: pathToFile error: &error];
With Swift 3, FileManager has a method called removeItem(at:). removeItem(at:) has the following declaration:
func removeItem(at URL: URL) throws
Removes the file or directory at the specified URL.
The code fragment below shows how to delete a file using removeItem(at:):
do {
try FileManager.default.removeItem(at: fileUrl)
} catch {
print("Could not delete file: \(error)")
}
As a complete example, the following UIViewController subclass implementation shows how to copy a file from the app bundle to a specific url in the document directory, delete a file at a specific url in document directory and check if a file exists at a specific url in document directory:
import UIKit
class ViewController: UIViewController {
#IBAction func copyFile(_ sender: UIButton) {
// Get app bundle file url
guard let bundleFileUrl = 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 bundle file to document directory
if !FileManager.default.fileExists(atPath: documentDirectoryFileUrl.path) {
do {
try FileManager.default.copyItem(at: bundleFileUrl, to: documentDirectoryFileUrl)
} catch {
print("Could not copy file: \(error)")
}
}
}
#IBAction func deleteFile(_ sender: UIButton) {
// Get 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")
// Delete file in document directory
if FileManager.default.fileExists(atPath: documentDirectoryFileUrl.path) {
do {
try FileManager.default.removeItem(at: documentDirectoryFileUrl)
} catch {
print("Could not delete file: \(error)")
}
}
}
#IBAction func checkIfFileExist(_ sender: UIButton) {
// Get destination url in document directory for file
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let outputUrl = documentsDirectory.appendingPathComponent("Movie.mov")
// Print if file exists in document directory or not
FileManager.default.fileExists(atPath: outputUrl.path) ? print("File exists") : print("File does not exist")
}
}
For Swift 3.x and Xcode 8.x
itemName - Name which you store the file in document directory
file extension - the extension of the file
func removeItemForDocument(itemName:String, fileExtension: String) {
let fileManager = FileManager.default
let NSDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let NSUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true)
guard let dirPath = paths.first else {
return
}
let filePath = "\(dirPath)/\(itemName).\(fileExtension)"
do {
try fileManager.removeItem(atPath: filePath)
} catch let error as NSError {
print(error.debugDescription)
}}
Related
I know there are a few questions pertaining to this, but they're in Objective-C.
How can I access a .txt file included in my app using Swift on an actual iPhone? I want to be able to read and write from it. Here are my project files if you want to take a look. I'm happy to add details if necessary.
Simply by searching in the app bundle for the resource
var filePath = NSBundle.mainBundle().URLForResource("file", withExtension: "txt")
However you can't write to it because it is in the app resources directory and you have to create it in the document directory to write to it
var documentsDirectory: NSURL?
var fileURL: NSURL?
documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last!
fileURL = documentsDirectory!.URLByAppendingPathComponent("file.txt")
if (fileURL!.checkResourceIsReachableAndReturnError(nil)) {
print("file exist")
}else{
print("file doesnt exist")
NSData().writeToURL(fileURL!,atomically:true)
}
now you can access it from fileURL
EDIT - 28 August 2018
This is how to do it in Swift 4.2
var filePath = Bundle.main.url(forResource: "file", withExtension: "txt")
To create it in the document directory
if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
let fileURL = documentsDirectory.appendingPathComponent("file.txt")
do {
if try fileURL.checkResourceIsReachable() {
print("file exist")
} else {
print("file doesnt exist")
do {
try Data().write(to: fileURL)
} catch {
print("an error happened while creating the file")
}
}
} catch {
print("an error happened while checking for the file")
}
}
Swift 3, based on Karim’s answer.
Reading
You can read files included in an app’s bundle through the bundle’s resource:
let fileURL = Bundle.main.url(forResource:"filename", withExtension: "txt")
Writing
However, you can’t write there. You will need to create a copy, preferably in the Documents directory:
func makeWritableCopy(named destFileName: String, ofResourceFile originalFileName: String) throws -> URL {
// Get Documents directory in app bundle
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
fatalError("No document directory found in application bundle.")
}
// Get URL for dest file (in Documents directory)
let writableFileURL = documentsDirectory.appendingPathComponent(destFileName)
// If dest file doesn’t exist yet
if (try? writableFileURL.checkResourceIsReachable()) == nil {
// Get original (unwritable) file’s URL
guard let originalFileURL = Bundle.main.url(forResource: originalFileName, withExtension: nil) else {
fatalError("Cannot find original file “\(originalFileName)” in application bundle’s resources.")
}
// Get original file’s contents
let originalContents = try Data(contentsOf: originalFileURL)
// Write original file’s contents to dest file
try originalContents.write(to: writableFileURL, options: .atomic)
print("Made a writable copy of file “\(originalFileName)” in “\(documentsDirectory)\\\(destFileName)”.")
} else { // Dest file already exists
// Print dest file contents
let contents = try String(contentsOf: writableFileURL, encoding: String.Encoding.utf8)
print("File “\(destFileName)” already exists in “\(documentsDirectory)”.\nContents:\n\(contents)")
}
// Return dest file URL
return writableFileURL
}
Example usage:
let stuffFileURL = try makeWritableCopy(named: "Stuff.txt", ofResourceFile: "Stuff.txt")
try "New contents".write(to: stuffFileURL, atomically: true, encoding: String.Encoding.utf8)
Just a quick update for using this code with Swift 4:
Bundle.main.url(forResource:"YourFile", withExtension: "FileExtension")
And the following has been updated to account for writing the file out:
var myData: Data!
func checkFile() {
if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
let fileURL = documentsDirectory.appendingPathComponent("YourFile.extension")
do {
let fileExists = try fileURL.checkResourceIsReachable()
if fileExists {
print("File exists")
} else {
print("File does not exist, create it")
writeFile(fileURL: fileURL)
}
} catch {
print(error.localizedDescription)
}
}
}
func writeFile(fileURL: URL) {
do {
try myData.write(to: fileURL)
} catch {
print(error.localizedDescription)
}
}
This particular example is not the most flexible, but with a little bit of work you can easily pass in your own file names, extensions and data values.
🎁 Property Wrapper - Fetch and convert to correct data type
This simple wrapper helps you to load any file from any bundle in a cleanest way:
#propertyWrapper struct BundleFile<DataType> {
let name: String
let type: String
let fileManager: FileManager = .default
let bundle: Bundle = .main
let decoder: (Data) -> DataType
var wrappedValue: DataType {
guard let path = bundle.path(forResource: name, ofType: type) else { fatalError("Resource not found: \(name).\(type)") }
guard let data = fileManager.contents(atPath: path) else { fatalError("Can not load file at: \(path)") }
return decoder(data)
}
}
Usage:
#BundleFile(name: "avatar", type: "jpg", decoder: { UIImage(data: $0)! } )
var avatar: UIImage
You can define any decoder to match your needs
Get File From Bundle in Swift 5.1
//For Video File
let stringPath = Bundle.main.path(forResource: "(Your video file name)", ofType: "mov")
let urlVideo = Bundle.main.url(forResource: "Your video file name", withExtension: "mov")
Bundles are read only. You can use NSBundle.mainBundle().pathForResource to access the file as read-only, but for read-write access you need to copy your document to Documents folder or tmp folder.
Bundles can be written. You can use Bundle.main.path to overwrite file by adding it into Copy Bundles Resource.
I have to use a file from another bundle. So, following code worked for me. Needful when you work with a frameworks.
let bundle = Bundle(for: ViewController.self)
let fileName = bundle.path(forResource: "fileName", ofType: "json")
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
}
}
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)")
}
}
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
}
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
}