Swift: Download a file using NSURLSession


The most important part of downloading a file is the NSURLSessionDownloadDelegate, because this is where you will make sure you have access to the file and so on.
class SessionDelegate:NSObject, NSURLSessionDownloadDelegate {

    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
        
        // see: https://github.com/AFNetworking/AFNetworking/issues/1635
        // First we obtain the path of the Documents directory
        if let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first
        {
            // next we retrieve the suggested name for the file
            let suggestedName = downloadTask.response?.suggestedFilename ?? "new_file.pdf"
            // now we can create the NSURL (including filename) - a file shouldn't already exist at this location
            let newLocation = NSURL(fileURLWithPath: documentsDirectoryPath).URLByAppendingPathComponent(suggestedName)
            
            do {
                // now we attempt to move the file from its temporary download location to the required location
                try NSFileManager.defaultManager().moveItemAtURL(location, toURL: newLocation)
            }
            catch {
                print("error moving file")
            }
        }
        
        
    }
    
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)    {
        print("did write data: \(bytesWritten)")
    }
    
    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
        print("task did complete")
        
    }
    
    
}
The most important method here is the one informing us when the download is finished. The others inform us of progress and completion.

Implementation code

With the session delegate in place, we can now start the download:
        // create a background session configuration with identifier
        let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("PDF Download")
        
        if let url = NSURL(string: "http://partners.adobe.com/public/developer/en/xml/AdobeXMLFormsSamples.pdf") {
            
            let delegate = SessionDelegate()
    
            // create session by instantiating with configuration and delegate
            let session = NSURLSession(configuration: config, delegate: delegate, delegateQueue: NSOperationQueue.mainQueue())
            
            let downloadTask = session.downloadTaskWithURL(url)
            
            downloadTask.resume()
            
        } 
With your device plugged into your Mac while the app is running, if you now go in Xcode to Window > Devices (or Shift + Cmd + 2) and select your device and then double-click on the app name you will see the folder and the file have been created.

Notes

There are download methods with completion handlers and also download methods that take NSURLRequests rather than URLs. When using a backgroundSessionConfiguration you can't use a method with a completion handler (and when using a completion handler method, rather than a backgroundSessionConfiguration, no delegate calls are made).

Extra

As an extra, in the following code I add a folder called "MyFolder" inside the documents directory path to which I move the file.

class SessionDelegate:NSObject, NSURLSessionDownloadDelegate {
    var baseFolder:NSURL!
   
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {

        // see: https://github.com/AFNetworking/AFNetworking/issues/1635
        // First we need to make sure that the folder in which we wish to place the file exists (note: you might want to do this ahead of time in a real app)
        if let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first where baseFolder == nil
        {
            let baseUrl = NSURL(fileURLWithPath:documentsDirectoryPath)
            baseFolder = baseUrl.URLByAppendingPathComponent("MyFolder")
            
            do {
                try NSFileManager.defaultManager().createDirectoryAtURL(baseFolder, withIntermediateDirectories: true, attributes: nil)
            }
            catch {
                print("error creating directory")
            }
        }
     
        // Next we retrieve the suggested name of our file and this file name to the URL (note we don't create this file beforehand)
        let suggestedName = downloadTask.response?.suggestedFilename ?? "new_file.pdf"
        let newLocation = baseFolder.URLByAppendingPathComponent(suggestedName)


        do {
            try NSFileManager.defaultManager().moveItemAtURL(location, toURL: newLocation)
        }
        catch {
            print("error moving file")
        }
        
    }
    
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        print("did write data: \(bytesWritten)")
    }
    
    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
        print("task did complete")

    }
    
    
}

Further note

If you do not wish to save the file directly you can open the file and manipulate the data before saving or utilising but you shouldn't do this on the main thread. (Worth comparing this with the data task variant of an NSURLSessionTask when choosing which is best for your purpose.)

Further reading

If you are uploading and downloading files in the background then you need to read about what happens when your app opens and closes.

Once you've got the grasp of downloading files, it's time to look at progress bars. Thankfully Apple has a sample app written in Swift called PhotoProgress that demonstrates the use of progress bars and is well worth investigation.

Comments