swift 5

Several years ago, I post an article to demostrate how to implement download file features in iOS. In that article, I am using swift 2 to write all features including start, pause, resume and show progress. Lots of readers ask me to improve a new version with swift4 or swift5. As the language changes a lot, some of the APIs has been deprecated. That’s the reason why I write this artiel. In this article, I will use the latest version of Swift (current version is 5) to show you how to implement download file features.

This is the tutorial 1 of iOS download manager in Swift5 tutorial. In this tutorial, I will give some example to show you how to download multiple files concurrently, and use UITableView and ProgressBar to manage each downloading task separately. You can also access the whole tutorial series with following links:

Tutorial 1 of Download Manager App: Download File in Swift5 Start Pause and Resume
Tutorial 2 of Download Manager App: Save and Load Downloaded Data Locally in iOS by Swift5
Tutorial 3 of Download Manager App: Download Multiple Files Simultaneously in iOS by Swift5

From shallow to deep, I will put 3 buttons on the stage. A start button will start the download task immediately when user clicks it. A pause button will pause the downloading task or resume the paused downloading task according to current state. A stop button will stop the downloading task totally which will not be resumed later. To show the downloading progress, I will put a progress bar as well. The following video shows how this downloading app works. The source code will perfectly support swift 5.

Put Button and Progress Bar on xib

To make this tutorial easy to read and understand, I will simply use xib file to arrange the UI components. In this section, I will drag and drop 1 label, 1 progress bar, and 3 buttons on the stage. This is a very simple task so that I will not take so much words on it. Here is my screenshot.

Link xib to ViewController

Next, I will create a ViewController with the same name as xib file. Then, declare all variables and link them to the UI components to xib. For example, I want to link the Pause button from xib to ViewController. So I open the ViewController in one window and xib file in another window. Then, clicking the small circle in the xib and draging to ViewController window.

Download File in Swift

In Swift 5, I can download files by URLSession in Swift. This class provides a set of methods which I can use to easily start the downloading task, puase and resume it. The following code is an example to show how to start a downloading session in swift.

if(!isDownload && downloadData == nil) {
    let url = URL.init(string: self.url)
    let request:URLRequest = URLRequest.init(url: url!)
    self.downloadTask = self.urlSession.downloadTask(with: request)
    self.downloadTask.resume()
    isDownload = true;
    progressLabel.text = "0%";
}

The above source code snapshot involves several classes including URL, URLRequest, URLSession and URLSessionDownloadTask. The logic is very easy to understand. In next section, I will show how to pause a downloading task.

Pause Download Task in Swift

Pause a download task is a little bit complicated, as when I call the cancel function of URLSessionDownloadTask, I can define a callback function which handles resume data for later use. The souce code is here:

self.downloadTask.cancel{resumeDataOrNil in
    guard let resumeData = resumeDataOrNil else {
        return
    }
    self.downloadData = resumeData
    self.downloadTask = nil
    self.isDownload = false
    
    self.pauseBtn.setTitle("Resume", for: UIControl.State.normal)
}

In this part, I meet with an error once I click the pause button. The error message is:

Exception:

Main Thread Checker: UI API called on a background thread: -[UIButton setTitle:forState:]
……
libc++abi.dylib: terminating with uncaught exception of type NSException

It is because I put the setting button state code in the callback function which is not allowed. Actually, there is a coding tip in the xcode:

TIPs:

UIButton.setTitle(_:for:) must be used from main thread only

That’s why the app is crashed when I click the pause button. The solution is dispatching the call to update the label text to the main thread. The fixed souce code likes like:

self.downloadTask.cancel{resumeDataOrNil in
    guard let resumeData = resumeDataOrNil else {
        return
    }
    self.downloadData = resumeData
    self.downloadTask = nil
    self.isDownload = false
    
    DispatchQueue.main.async {
        self.pauseBtn.setTitle("Resume", for: UIControl.State.normal)
    }
}

Stop Download Task in Swift

Stop a download task is similar with pause task, but it doesn’t need to handle the resume data. So in stop task, I just cancel the download task and reset all UI component states.

@IBAction func stopDownloadSingleFile(sender: AnyObject) {
    self.downloadTask?.cancel();
    self.downloadData = nil;
    self.isDownload = false;
    self.downloadTask = nil;
    self.progressBar.progress = 0;
    self.progressLabel.text = "0%";
    
    self.playBtn.isEnabled = true
    self.pauseBtn.isEnabled = false
    self.stopBtn.isEnabled = false
    self.pauseBtn.setTitle("Pause", for: UIControl.State.normal);
}

Watch Download Progress in Swift

In this demo, I have a progress view and progress label to show the download progress. To watch the download’s progress, I extends the URLSessionDownloadDelegate on ViewController class. After that, I can receive the download progress updates in Swift.

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
    //Receiving Progress Updates
    let written:CGFloat = (CGFloat)(totalBytesWritten)
    let total:CGFloat = (CGFloat)(totalBytesExpectedToWrite)
    let pro:CGFloat = written/total
    DispatchQueue.main.async {
        self.progressBar.progress = Float(pro)
        self.progressLabel.text = "\(Int(floor(pro * 100)))%";
    }
}

Once the download task is completed, I will set the progress to 100% and set all button disable.

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    print("Finished")
    print("location:\(location)")
    
    DispatchQueue.main.async {
        self.progressBar.progress = 1.0
            self.progressLabel.text = "100%";
        self.playBtn.isEnabled = false
        self.pauseBtn.isEnabled = false
        self.stopBtn.isEnabled = false
    }
}

After fully tested, now this demo app is working perfectly. You can start the download task at any time, pause or resume it at any time and stop it if you want. The source code is completely writen in swift 5.

Download Source Code at $2.99

Now you can get the full source code under $2.99 with all copyright. You can get the whole project, run the code in your Xcode, use it in your own project at any time. The payment is handling by PayPal. When you complete the purchase, you will get the download link of the source code.

Previous PostNext Post

Leave a Reply

Your email address will not be published. Required fields are marked *