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.


Discover more from SweetTutos

Subscribe to get the latest posts sent to your email.

Malek
Software craftsman with extensive experience in iOS and web development.