How do I download a file in Alamofire 4 and save to the Documents directory?
Solution 1
Why are you performing .validate
? You are not storing any data after download in your current code.
Alamofire allows you to store a file directly after download:
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendPathComponent("pig.png")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, to: destination).response { response in
print(response)
if response.result.isSuccess, let imagePath = response.destinationURL?.path {
let image = UIImage(contentsOfFile: imagePath)
}
}
And by the way, the download path you are providing in the download
method, is the local URL to the Documents directory and not a URL of a server.
Solution 2
Swift 3.x and Alamofire 4.x version
Well certainly the Alamofire
example posted by Alamofire itself has bugs. Since fileURL
returns Void
it can't be used as a parameter in return statement.
Also remove .createIntermediateDirectories
from option list of return statement if you don't want any Directories for the file you downloaded
EDIT
If you want to know the type of file, simply grab the last component part and convert String
to NSString
as NSString
have these functionalities.
//audioUrl should be of type URL
let audioFileName = String((audioUrl?.lastPathComponent)!) as NSString
//path extension will consist of the type of file it is, m4a or mp4
let pathExtension = audioFileName.pathExtension
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
// the name of the file here I kept is yourFileName with appended extension
documentsURL.appendPathComponent("yourFileName."+pathExtension)
return (documentsURL, [.removePreviousFile])
}
Alamofire.download("yourAudioUrl", to: destination).response { response in
if response.destinationURL != nil {
print(response.destinationURL!)
}
}
Output is
file:///Users/rajan/Library/Developer/CoreSimulator/Devices/92B4AB6E-92C0-4864-916F-9CB8F9443014/data/Containers/Data/Application/781AA5AC-9BE7-46BB-8DD9-564BBB343F3B/Documents/yourFileName.mp3
which is the actual path of your file where it is stored.
Solution 3
Swift 5 - Alamofire 5.1. This is the approach.
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("image.png")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download("https://httpbin.org/image/png", to: destination).response { response in
debugPrint(response)
if response.error == nil, let imagePath = response.fileURL?.path {
let image = UIImage(contentsOfFile: imagePath)
}
}
Solution 4
Although the question is an older one,
I have re-written and tested it on Swift 5
import Foundation
import Alamofire
class DownloadFileService {
static func downloadFile(using url: URL, completion: @escaping () -> Void) {
let fileName = url.lastPathComponent
let destination: DownloadRequest.Destination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent(fileName)
return (documentsURL, [.removePreviousFile])
}
AF.download(url, to: destination).response { response in
print(response)
completion()
}
}
}
Solution 5
Objective: Downloaded files from the server like gif, pdf, or zip will be store inside your specified folder name.
If you want to store your own folder structure like the name is "ZipFiles"
call .
self downloadZipFileFromServer(downloadFolderName: "ZipFiles");
Downloaded zip data is stored inside the document/ZiFiles/abc.zip
this just creates a folder inside the document
func createFolder(folderName:String)
Alamofire 4
Swift 4
/******Download image/zip/pdf from the server and save in specific Dir********/
func downloadZipFileFromServer(downloadFolderName: string)
{
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var fileURL = self.createFolder(folderName: downloadFolderName)
let fileName = URL(string : "www.xymob.com/abc.zip")
fileURL = fileURL.appendingPathComponent((fileName?.lastPathComponent)!)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download("www.xymob.com/abc.zip", to: destination).response(completionHandler: { (DefaultDownloadResponse) in
print("res ",DefaultDownloadResponse.destinationURL!);
})
}
func createFolder(folderName:String)->URL
{
var paths: [Any] = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory: String = paths[0] as? String ?? ""
let dataPath: String = URL(fileURLWithPath: documentsDirectory).appendingPathComponent(folderName).absoluteString
if !FileManager.default.fileExists(atPath: dataPath) {
try? FileManager.default.createDirectory(atPath: dataPath, withIntermediateDirectories: false, attributes: nil)
}
let fileURL = URL(string: dataPath)
return fileURL!
}
Comments
-
Kex over 3 years
I'm trying to use
Alamofire4
forSwift3
. I need to download .mp3
files and save them to theDocuments
directory. Current code is like so:func downloadAudioFromURL(url: String, completion: ((_ status: ResponseStatus, _ audioLocalURL: URL?) -> Void)?) { let fileManager = FileManager.default let directoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] let audioFullURL = String.ensureFullURLPath(url) alamoManager.download(audioFullURL) .validate { request, response, temporaryURL, destinationURL in var pathComponent = response.suggestedFilename! if pathComponent == "m4a.mp4" { // Due to the old Android audio files without a filename // Assign a unique name so audio files don't overwrite each other pathComponent = "\(NSUUID().uuidString).mp4" } let localURL = directoryURL.appendingPathComponent(pathComponent) if response.statusCode == 200 { completion?(.success, localURL) } else { completion?(.failure, nil) } return .success } .responseJSON { response in debugPrint(response) print(response.temporaryURL) print(response.destinationURL) } }
However I can't actually access the files from the
localURL
after saving. I have also noticed that thelocalURL
will be exactly the same for different files I try to download (maybe they are overwriting?). E.g:file:///Users/testuser/Library/Developer/CoreSimulator/Devices/D4254AEA-76DD-4F01-80AF-F1AF3BE8A204/data/Containers/Data/Application/29755154-DD21-4D4C-B340-6628607DC053/Documents/file1.mp3
Any ideas what I am doing wrong here?
Edited my code to look like this:
func downloadAudioFromURL(url: String, completion: ((_ status: ResponseStatus, _ audioLocalURL: URL?) -> Void)?) { let destination: DownloadRequest.DownloadFileDestination = { _, _ in var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] documentsURL.appendPathComponent("Audiofile.mp3") return (documentsURL, [.removePreviousFile]) } Alamofire.download(url, to: destination).response { response in if let localURL = response.destinationURL { completion?(.success, localURL) } else { completion?(.failure, nil) } } }
How would I check for m4a.mp4 though?
-
Kex over 7 yearsOn the return line I have the error
Cannot convert return expression of type (), to type URL
. Also, I see you are setting the path component before the download, but actually I want to use the recommended path component. -
Rajan Maheshwari over 7 years@Kex See my answer for this fix
Cannot convert return expression of type (), to type URL
-
ezcoding over 7 years@Kex How are you going to access the file afterwards, if you don't know the name of it? Even if you only have one file, I'd strongly recommend that you provide the name yourself. Then answer I've provided is the recommended way by Alamofire. I don't see the point in trying to use it differently as it was intended to be used.
-
Kex over 7 yearsPlease see my edited question. I will add a name but I also need to check pathComponent for if it is
m4a.mp4
. Is it possible to do this before calling download? -
Rajan Maheshwari over 7 yearsYou want to check about the file type whether its m4a or mp4? I believe you are hitting the url and from that URL you can easily access the last component which can tell you its mp4 or m4a
-
ezcoding over 7 years
.validate
allows you check for a MIME Type. But this is a different question. First try to successfully download any kind of file. If you succeed in that, start using validate. How to use.validate
to check if a file is .m4a or .mp3 is a different question though. -
Rajan Maheshwari over 7 years@Kex From your audioURL we can get the extension and make the document path for saving the file accordingly. See the edit.
-
Jordan Montel about 7 yearsdid you already got this error on the line "documentsURL.appendPathComponent" : "libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread" ?
-
Fox5150 about 7 yearsAlamofire example code itself has bugs ! You are absolutely right @Rajan ! I've lost all day trying to make this example working. Your solution work, thanks.
-
Praburaj over 6 yearsCan anyone tell how to open the file using the destination url after the file has been downloaded
-
Cid almost 6 yearsPosting code without any explanation isn't welcome here. Would you please complete your answer ?
-
Sakthimuthiah over 4 yearsIn filePath - i have used as follows let documentsDirectory = "file://(documentsDirectory)"