Networking in Swift: How to download a file with URLSession [Swift 3]

Update November 2016: Fully updated for Xcode 8 and Swift 3.

You will often need to download content to use in your app. NSURLConnection used to be a great interface to perform networking tasks along with NSURLSession. But since it’s deprecated in iOS 9, Apple recommends you use the URLSession class from now on for all server operations, like http requests, ftp operations and file download/upload tasks.

In this tutorial, you gonna use the URLSession download task to download a file remotely to your app and report the progress to the user while bytes are being downloaded.

Let’s begin by downloading the starter project here.

The project you will build by the end of this tutorial will download a PDF file and load it on the screen.

If you take a look at the user interface in the starter project, there is already four buttons to manage the download operation. The common states for a download task are the pause/resume/stop and start actions. There is also a progress view to report the download progress instantly to the user.

The URLSession hierarchy requires a specific configuration. When a session object is about to be created, it needs a configuration object that define the behavior and policies to use when uploading and downloading content. In this tutorial, you gonna use a background session configuration in order to allow HTTP download to be performed in the background.

Select the ViewController.swift file from the Project navigator view and add the following properties declarations:

    var downloadTask: URLSessionDownloadTask!
    var backgroundSession: URLSession!

The properties above are declared as implicitly unwrapped optionals which explains why you put the exclamation mark after their types. Declaring the properties this way will relieve us from using the question mark (?) each time you use the property to unwrap its value, since we know the property value is confirmed to exist immediately after the optional is first defined.

Next, locate the viewDidLoad method and implement the following code inside:

let backgroundSessionConfiguration = URLSessionConfiguration.background(withIdentifier: "backgroundSession")
backgroundSession = Foundation.URLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: OperationQueue.main)
progressView.setProgress(0.0, animated: false)

The code above will instantiate a background session configuration object and use it to create an URLSession instance. As I said earlier, the background session configuration is what enables the session object to carry on the download in the background.

You also set the progress property of the UIProgressView object to 0 to get ready for the download operation.

Good, now locate the startDownload action method and copy the following code inside:

let url = URL(string: "http://publications.gbdirect.co.uk/c_book/thecbook.pdf")!
downloadTask = backgroundSession.downloadTask(with: url)
downloadTask.resume()

This will construct an URL object for a “PDF ebook”. Then it will instantiate an URLSessionDownloadTask object to initiate and manage the download operation of the file. Calling the resume method will start the download task immediately.

Starting the download operation will imply some events to be raised by the download task object (i.e: Bytes are being downloaded, file did finish downloading, etc). To react to such events accordingly, you will need to implement some protocol methods.

First, make the class conform to the URLSessionDownloadDelegate protocol by changing the class declaration to the following:

class ViewController: UIViewController, URLSessionDownloadDelegate {

Next, implement the following URLSessionDownloadDelegate protocol methods before the class closing bracket:

// 1
func urlSession(_ session: URLSession,
                    downloadTask: URLSessionDownloadTask,
                    didFinishDownloadingTo location: URL){
        
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentDirectoryPath:String = path[0]
let fileManager = FileManager()
let destinationURLForFile = URL(fileURLWithPath: documentDirectoryPath.appendingFormat("/file.pdf"))
        
if fileManager.fileExists(atPath: destinationURLForFile.path){
     showFileWithPath(path: destinationURLForFile.path)
    }
else{
      do {
           try fileManager.moveItem(at: location, to: destinationURLForFile)
           // show file
           showFileWithPath(path: destinationURLForFile.path)
      }catch{
           print("An error occurred while moving file to destination url")
      }
   }
}
// 2
func urlSession(_ session: URLSession,
               downloadTask: URLSessionDownloadTask,
               didWriteData bytesWritten: Int64,
               totalBytesWritten: Int64,
               totalBytesExpectedToWrite: Int64){
   progressView.setProgress(Float(totalBytesWritten)/Float(totalBytesExpectedToWrite), animated: true)
}

Let’s breakdown the code above:

// 1: This protocol method will tell the delegate that the download task has finished downloading. You just checked whether the file already exists. If so, the code will load the file immediately, otherwise, it will move it to a new location before loading it.

Embedding the moveItem method inside a do-catch bloc is mandatory since this method may throw errors.

// 2: Here you just benefit from the useful informations this protocol method gives you. Precisely the totalBytesWritten and totalBytesExpectedToWrite parameters. As the method periodically informs the delegate about the download progress, you just calculated the downloaded bytes and updated the progress view accordingly.

Let’s implement the showFileWithPath method which will load the PDF file once it’s downloaded. Copy the following code inside the class:

func showFileWithPath(path: String){
    let isFileFound:Bool? = FileManager.default.fileExists(atPath: path)
    if isFileFound == true{
        let viewer = UIDocumentInteractionController(url: URL(fileURLWithPath: path))
        viewer.delegate = self
        viewer.presentPreview(animated: true)
    }
}

The method above will use the path string to load the file using the UIDocumentInteractionController class.

UIDocumentInteractionController requires a delegate as well as implementing a UIDocumentInteractionControllerDelegate protocol method in order to be able to load a preview screen for the file.

Copy the following protocol method before the class closing bracket:

func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController
{
    return self
}

The method above will return the current view controller as the preview controller of the PDF file.

Next, change the class declaration to the following:

class ViewController: UIViewController, URLSessionDownloadDelegate, UIDocumentInteractionControllerDelegate {

Each URLSession task may encounter an issue and end up with an error, it’s recommended that we react to such scenario accordingly. To do so, copy the following URLSessionTaskDelegate protocol method inside the class:

func urlSession(_ session: URLSession,
                 task: URLSessionTask,
                 didCompleteWithError error: Error?){
   downloadTask = nil
   progressView.setProgress(0.0, animated: true)
   if (error != nil) {
       print(error!.localizedDescription)
   }else{
       print("The task finished transferring data successfully")
   }
}

Let’s finish up by implementing the remaining action methods for the pause/resume/cancel operations.

Change the pause, the resume and the cancel methods implementations to the following:

@IBAction func pause(sender: AnyObject) {
        if downloadTask != nil{
            downloadTask.suspend()
        }
}
@IBAction func resume(sender: AnyObject) {
        if downloadTask != nil{
            downloadTask.resume()
        }
}
@IBAction func cancel(sender: AnyObject) {
        if downloadTask != nil{
            downloadTask.cancel()
        }
}

Before you run the project, let’s disable the App Transport Security so that it won’t block access to the URL. Select the Info.plist file from the Project navigator view and make the following changes:

Disable the app transport security (ATS)

That’s it, run the project and enjoy your work once again 🙂

As usual, you can download the final project here.

Feel free to leave a comment below, I’d love to hear from you.

Also, the tutorial forum thread is here in case you have related questions. Feel free to open a new thread if you want to ask me other technical questions, I am here to help you.

Malek
iOS developer with over than 11 years of extensive experience working on several projects with different sized startups and corporates.

10 Comments

  1. The app compiles and runs on an iPhone 6 Plus and downloads the PDF but unfortunately the progress bar shows nothing. What can I do to correct this?

  2. Hi Andy, I tested again with Xcode 7 and Xcode 8 and the progress bar is running correctly. Are you using a different code than in the tutorial?

  3. Hi Malek

    Thanks for reply and I thought I hadn’t change anything but download tutorial again and you are correct it runs perfectly, sorry I raised comment. I now have learn to use this tutorial code to add the feature to my JSON download.

  4. hi andy, i want to learn download sound from url, but many file and the name based from database. what this tutorial can be used? thanks

  5. I have problem , I have 2 tableview , both using same ViewController , because I got the data from server and change the table depend of cell i clicked on first tableview , If i Download files it downloaded without problem , but when I click Back button and select another Cell then try to download I got this error
    “A background URLSession with identifier backgroundSession already exists!”
    and the Progress view won’t update but the file is downloaded !!

    this problem happen every time I go back and return to same view controller

    I hope you are understand my problem

  6. Perfect. Thanks. Clear and concise.

    I am using with Swift 4. I just pasted your code snippets into the right spots in my code, added the protocol extensions to my class declarations where needed, and it ran almost without modification. Fastest way that I could find to get started on file downloads. Saved me heaps of time.

Comments are closed.