How do I download a file in Alamofire 4 and save to the Documents directory?

23,231

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!
}
Share:
23,231
Kex
Author by

Kex

Swift. Objective C. Python.

Updated on December 27, 2020

Comments

  • Kex
    Kex over 3 years

    I'm trying to use Alamofire4 for Swift3. I need to download .mp3 files and save them to the Documents 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 the localURL 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
    Kex over 7 years
    On 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
    Rajan Maheshwari over 7 years
    @Kex See my answer for this fix Cannot convert return expression of type (), to type URL
  • ezcoding
    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
    Kex over 7 years
    Please 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
    Rajan Maheshwari over 7 years
    You 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
    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
    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
    Jordan Montel about 7 years
    did 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
    Fox5150 about 7 years
    Alamofire 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
    Praburaj over 6 years
    Can anyone tell how to open the file using the destination url after the file has been downloaded
  • Cid
    Cid almost 6 years
    Posting code without any explanation isn't welcome here. Would you please complete your answer ?
  • Sakthimuthiah
    Sakthimuthiah over 4 years
    In filePath - i have used as follows let documentsDirectory = "file://(documentsDirectory)"