How to download file in swift?

141,245

Solution 1

Example downloader class without Alamofire:

class Downloader {
    class func load(URL: NSURL) {
        let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
        let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
        let request = NSMutableURLRequest(URL: URL)
        request.HTTPMethod = "GET"
        let task = session.dataTaskWithRequest(request, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
            if (error == nil) {
                // Success
                let statusCode = (response as NSHTTPURLResponse).statusCode
                println("Success: \(statusCode)")

                // This is your file-variable:
                // data
            }
            else {
                // Failure
                println("Failure: %@", error.localizedDescription);
            }
        })
        task.resume()
    }
}

This is how to use it in your own code:

class Foo {
    func bar() {
        if var URL = NSURL(string: "http://www.mywebsite.com/myfile.pdf") {
            Downloader.load(URL)
        }
    }
}

Swift 3 Version

Also note to download large files on disk instead instead in memory. see `downloadTask:

class Downloader {
    class func load(url: URL, to localUrl: URL, completion: @escaping () -> ()) {
        let sessionConfig = URLSessionConfiguration.default
        let session = URLSession(configuration: sessionConfig)
        let request = try! URLRequest(url: url, method: .get)

        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("Success: \(statusCode)")
                }

                do {
                    try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
                    completion()
                } catch (let writeError) {
                    print("error writing file \(localUrl) : \(writeError)")
                }

            } else {
                print("Failure: %@", error?.localizedDescription);
            }
        }
        task.resume()
    }
}

Solution 2

Swift 4 and Swift 5 Version if Anyone still needs this

import Foundation

class FileDownloader {

    static func loadFileSync(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 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()
        }
    }
}

Here is how to call this method :-

let url = URL(string: "http://www.filedownloader.com/mydemofile.pdf")
FileDownloader.loadFileAsync(url: url!) { (path, error) in
    print("PDF File downloaded to : \(path!)")
}

Solution 3

Here's an example that shows how to do sync & async.

import Foundation

class HttpDownloader {

    class func loadFileSync(url: NSURL, completion:(path:String, error:NSError!) -> Void) {
        let documentsUrl =  NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as! NSURL
        let destinationUrl = documentsUrl.URLByAppendingPathComponent(url.lastPathComponent!)
        if NSFileManager().fileExistsAtPath(destinationUrl.path!) {
            println("file already exists [\(destinationUrl.path!)]")
            completion(path: destinationUrl.path!, error:nil)
        } else if let dataFromURL = NSData(contentsOfURL: url){
            if dataFromURL.writeToURL(destinationUrl, atomically: true) {
                println("file saved [\(destinationUrl.path!)]")
                completion(path: destinationUrl.path!, error:nil)
            } else {
                println("error saving file")
                let error = NSError(domain:"Error saving file", code:1001, userInfo:nil)
                completion(path: destinationUrl.path!, error:error)
            }
        } else {
            let error = NSError(domain:"Error downloading file", code:1002, userInfo:nil)
            completion(path: destinationUrl.path!, error:error)
        }
    }

    class func loadFileAsync(url: NSURL, completion:(path:String, error:NSError!) -> Void) {
        let documentsUrl =  NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as! NSURL
        let destinationUrl = documentsUrl.URLByAppendingPathComponent(url.lastPathComponent!)
        if NSFileManager().fileExistsAtPath(destinationUrl.path!) {
            println("file already exists [\(destinationUrl.path!)]")
            completion(path: destinationUrl.path!, error:nil)
        } else {
            let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
            let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
            let request = NSMutableURLRequest(URL: url)
            request.HTTPMethod = "GET"
            let task = session.dataTaskWithRequest(request, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
                if (error == nil) {
                    if let response = response as? NSHTTPURLResponse {
                        println("response=\(response)")
                        if response.statusCode == 200 {
                            if data.writeToURL(destinationUrl, atomically: true) {
                                println("file saved [\(destinationUrl.path!)]")
                                completion(path: destinationUrl.path!, error:error)
                            } else {
                                println("error saving file")
                                let error = NSError(domain:"Error saving file", code:1001, userInfo:nil)
                                completion(path: destinationUrl.path!, error:error)
                            }
                        }
                    }
                }
                else {
                    println("Failure: \(error.localizedDescription)");
                    completion(path: destinationUrl.path!, error:error)
                }
            })
            task.resume()
        }
    }
}

Here's how to use it in your code:

let url = NSURL(string: "http://www.mywebsite.com/myfile.pdf") 
HttpDownloader.loadFileAsync(url, completion:{(path:String, error:NSError!) in
                println("pdf downloaded to: \(path)")
            })

Solution 4

If you need to download only text file into String you can use this simple way, Swift 5:

let list = try? String(contentsOf: URL(string: "https://example.com/file.txt")!)

In case you want non optional result or error handling:

do {
    let list = try String(contentsOf: URL(string: "https://example.com/file.txt")!)
}
catch {
    // Handle error here
}

You should know that network operations may take some time, to prevent it from running in main thread and locking your UI, you may want to execute the code asynchronously, for example:

DispatchQueue.global().async {
    let list = try? String(contentsOf: URL(string: "https://example.com/file.txt")!)
}

Solution 5

Devran's and djunod's solutions are working as long as your application is in the foreground. If you switch to another application during the download, it fails. My file sizes are around 10 MB and it takes sometime to download. So I need my download function works even when the app goes into background.

Please note that I switched ON the "Background Modes / Background Fetch" at "Capabilities".

Since completionhandler was not supported the solution is not encapsulated. Sorry about that.

--Swift 2.3--

import Foundation 
class Downloader : NSObject, NSURLSessionDownloadDelegate
{
    var url : NSURL? 
    // will be used to do whatever is needed once download is complete
    var yourOwnObject : NSObject?

    init(yourOwnObject : NSObject)
    {
        self.yourOwnObject = yourOwnObject
    }

    //is called once the download is complete
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL)
    {
        //copy downloaded data to your documents directory with same names as source file
        let documentsUrl =  NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
        let destinationUrl = documentsUrl!.URLByAppendingPathComponent(url!.lastPathComponent!)
        let dataFromURL = NSData(contentsOfURL: location)
        dataFromURL?.writeToURL(destinationUrl, atomically: true)

        //now it is time to do what is needed to be done after the download
        yourOwnObject!.callWhatIsNeeded()
    }

    //this is to track progress
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
    {
    }

    // if there is an error during download this will be called
    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?)
    {
        if(error != nil)
        {
            //handle the error
            print("Download completed with error: \(error!.localizedDescription)");
        }
    }

    //method to be called to download
    func download(url: NSURL)
    {
        self.url = url

        //download identifier can be customized. I used the "ulr.absoluteString"
        let sessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(url.absoluteString)
        let session = NSURLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
        let task = session.downloadTaskWithURL(url)
        task.resume()
    }
}

And here is how to call in --Swift 2.3--

    let url = NSURL(string: "http://company.com/file.txt")
    Downloader(yourOwnObject).download(url!)

--Swift 3--

class Downloader : NSObject, URLSessionDownloadDelegate {

var url : URL?
// will be used to do whatever is needed once download is complete
var yourOwnObject : NSObject?

init(_ yourOwnObject : NSObject)
{
    self.yourOwnObject = yourOwnObject
}

//is called once the download is complete
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
{
    //copy downloaded data to your documents directory with same names as source file
    let documentsUrl =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    let destinationUrl = documentsUrl!.appendingPathComponent(url!.lastPathComponent)
    let dataFromURL = NSData(contentsOf: location)
    dataFromURL?.write(to: destinationUrl, atomically: true)

    //now it is time to do what is needed to be done after the download
    yourOwnObject!.callWhatIsNeeded()
}

//this is to track progress
private func URLSession(session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
{
}

// if there is an error during download this will be called
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
{
    if(error != nil)
    {
        //handle the error
        print("Download completed with error: \(error!.localizedDescription)");
    }
}

//method to be called to download
func download(url: URL)
{
    self.url = url

    //download identifier can be customized. I used the "ulr.absoluteString"
    let sessionConfig = URLSessionConfiguration.background(withIdentifier: url.absoluteString)
    let session = Foundation.URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
    let task = session.downloadTask(with: url)
    task.resume()
}}

And here is how to call in --Swift 3--

    let url = URL(string: "http://company.com/file.txt")
    Downloader(yourOwnObject).download(url!)
Share:
141,245
r_19
Author by

r_19

Updated on July 05, 2022

Comments

  • r_19
    r_19 almost 2 years

    I just started learning apple swift programming for iOS coming from android. I basically can now read and manipulate swift code and also learned some common classes used in iOS swift programming but still having some confusion with the syntax and everything.

    I'm trying to download file. Like, lets just say coming from this URL

    var url = "http://www.mywebsite.com/myfile.pdf"
    

    in a button click. Maybe with visual progress too

    Through searching here in stackoverflow, I stumbled upon Alamofire. I might try it but I'm not sure if this is the best way for me to do it.

    So, I would like to ask how and what are my options (iOS7 and iOS8) in achieving my goal. Also, pros and cons would be awesome!

  • r_19
    r_19 about 9 years
    Hi Devran! I tried your code and it is showing 'Success: 200'. I have some few other questions regarding with the code: 1. Does 'Success: 200' means the file is downloaded or that the URL is valid? 2. Is there a way to show a download progress in the notification bar? 3. Where is the file downloaded in the device (just using the emulator)? I need to check if the file is present. Thank you for time some time answering my issue. I will check out NSData as you recommended as well
  • Devran Cosmo Uenal
    Devran Cosmo Uenal about 9 years
    1. Technically it means both. Success 200 stands for: the request to that url was accepted and could be answered from the server without errors. (200 is the http status code for OK) see: List of HTTP codes on Wikipedia
  • Devran Cosmo Uenal
    Devran Cosmo Uenal about 9 years
    2. For things like that, I would recommend using Alamofire. On the github page of Alamofire, there's an example how to download a file and return the current progress. it also helps you to write less code with the same results :) see: Downloading a File w/Progress.
  • Devran Cosmo Uenal
    Devran Cosmo Uenal about 9 years
    3. Where is the file downloaded in the device (just using the emulator)? I need to check if the file is present. The file is you've downloaded is accessible via code. it's up to you to save it to disk or process the contents of your file in memory. the variable data in my example code contains the whole pdf in binary. to return the contents of data, just write println(data) To learn how to save files on ios, just take a look at: https://github.com/sketchytech/FileSave (there's also a swift version)
  • r_19
    r_19 about 9 years
    Thanks Devran! This is really very helpful! Appreciate it! Is there any chance you know a good PDF reader library in swift or how to display pdf in ios? I need to implement a pdf reader on my app so I can implement banner ads in the pdf reader :)
  • Devran Cosmo Uenal
    Devran Cosmo Uenal about 9 years
    In that case, you could display your pdf in a WebView :) btw. if i could answer your questions, please accept my answer. see thanks.
  • r_19
    r_19 about 9 years
    Sure! Thanks for the help!
  • Daniel Qiu
    Daniel Qiu about 9 years
    @DevranCosmoUenal, now that we have the binary data of pdf file in the memory, it there any way to load the data in the browser and render it as PDF, without saving the file to disk?
  • Sashi
    Sashi about 9 years
    @DevranCosmoUenal : I was looking in to this answer and wanted to ask if you would know on how to add headers while using alamofire download function.
  • Devran Cosmo Uenal
    Devran Cosmo Uenal about 9 years
    @DanielQiu this should help: stackoverflow.com/questions/16806739/…
  • Devran Cosmo Uenal
    Devran Cosmo Uenal about 9 years
    @Sashi sorry, no idea :(
  • Thiha Aung
    Thiha Aung about 8 years
  • jackbravo
    jackbravo over 7 years
    You could use a swift protocol to use a delegate and encapsulate your solutions a little bit more.
  • los.adrian
    los.adrian over 7 years
    @DevranCosmoUenal How could I use your swift3 code? How could i call that method? Sorry, I am so new at iOS!
  • Shree Prakash
    Shree Prakash over 7 years
    @DevranCosmoUenal Hello Sir , I want to add Password Protected after download file, Please guide me.
  • Kavin Kumar Arumugam
    Kavin Kumar Arumugam almost 7 years
    @DevranCosmoUenal I'm getting error in .get method. I need to add any library?
  • Kavin Kumar Arumugam
    Kavin Kumar Arumugam almost 7 years
    I'm Getting Error in downloadProgress: Use of unresolved identifier. How to Fix it? What is the type of downloadProgress variable
  • Devran Cosmo Uenal
    Devran Cosmo Uenal almost 7 years
    No library is required -- but the Code might be now incompatible with the most current version of Swift.
  • Vivek
    Vivek almost 7 years
    downloadProgress is UIProgressView class
  • Pintu Rajput
    Pintu Rajput almost 7 years
    And what about the url and localUrl ?
  • Lohith Korupolu
    Lohith Korupolu over 6 years
    and where is this file saved to?
  • Ahmet Akkök
    Ahmet Akkök over 6 years
    It downloads the file to documents folder of your application. You can customize that in urlSession method though.
  • Pintu Rajput
    Pintu Rajput over 6 years
    But major task is how to hide from device, Data can be access through app only.
  • Abdelrahman Mohamed
    Abdelrahman Mohamed almost 6 years
    Hey How to use localUrl how to write it?
  • Naresh
    Naresh almost 5 years
    how to download audio file from url and save it in library?
  • iOSDude
    iOSDude over 4 years
    HI, This won't work for background downloading(Home btn, locked case) can you provide solution to work with background data downloading in swift?
  • Dani
    Dani over 3 years
    What is localURL? The filename I choose?