Downloading files is a common task in most of the iOS app. If you are building an ebook reader, or a comic book reader app, downloading ebooks will be a necessary feature. Last time, I am using a news reader app which will download the pdf newspaper from their website. This feature is also built in music player apps to download mp3 file, video player apps to download video file, even in the wallpaper apps to download wallpapers.
Note: Updated in Swift 5
I have updated this tutorial in swift 5. Please check the new version here:
Download File in Swift5 Start Pause and Resume
The original content here is out of date. Please visit the above links for the latest tutorials.
As my learning concept, it is always the best way to learning a new technical through a real project. Therefore, I will build a real download manager app from the ground up. This is the tutorial 1 of Download Manager iOS App. You can also access the whole tutorial sessions by following link:
Tutorial 1 of Download Manager App: Start, Pause, Resume and Stop Download in iOS
Tutorial 2 of Download Manager App: Save and Load Downloaded Data Locally in iOS
Tutorial 3 of Download Manager App: Download Multiple Files Simultaneously in iOS
To implement a full download manager, it must be able to start a download task, pause a download task and resume the download task. In this tutorial, I will implement a download manager which has all these functions. I will try my best to make this tutorial easy to understand so it could benefits all people.
By the way, as Apple recommends their new language Swift, I will build all my iOS project with Swift. For people who were using objective-c, I think it’s time to change your habit. I understand the pain for people who have very long experience on objective-c. But there is no choice, Apple want us to use Swift.
Download Files in iOS By Swift
In my plan, I will start this tutorial with a simple app first. In this app, I will put three buttons on the stage, start button, pause button and cancel button. When users click on the start downloading button, the app will start to download a file from internet. User also can pause the downloading task or cancel the downloading task at any time. When the downloading task is over, the app will notice the user with an alter view. Additionally, the app will also show a progress bar to show the download progress when downloading task begins. Now, let’s build the simple UI on the xib. Check xib vs storyboard for the reason why I am using xib instead of storyboard.
Launch Customized xib as Default App Screen
Now Apple recommends Storyboard to build App UI. If we want to launch xib as iOS app first screen UI, we need to explicitly tell application to load the xib.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. let screenBounds:CGRect = UIScreen.mainScreen().bounds; self.window = UIWindow.init(frame: screenBounds); self.window?.autoresizesSubviews = true; self.viewController = DownloadViewController(); self.window?.rootViewController = self.viewController; self.window?.makeKeyAndVisible(); return true; }
Download File By NSURLSession in Swift
There are lots of Objective-C downloading file example on internet, but it’s very hard to find complete Swift downloading file examples. In this tutorial, I will write an example in Swift to demonstrate downloading file by NSURLSession.
First, I am using following piece of code to start to download a file from website.
@IBAction func startDownloadSingleFile(sender: AnyObject) { var urlSession:NSURLSession!; var urlConfiguration:NSURLSessionConfiguration!; var downloadTask:NSURLSessionDownloadTask!; var downloadData:NSData!; urlConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration(); let queue:NSOperationQueue = NSOperationQueue.mainQueue(); urlSession = NSURLSession.init(configuration: urlConfiguration, delegate: self, delegateQueue: queue); let url:NSURL = NSURL(string: downloalURL)!; downloadTask = urlSession.downloadTaskWithURL(url); downloadTask.resume(); }
Then, we start to implement several delegate functions to accept the SSL handshake, watch the download progress, and handle the file after downloading is successful.
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) { let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!); completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential); }
The above function will be called once we try to download file from HTTPS hosting. This is a very simple example. For more sophisticated implementation, we can add more logic like compare host name or create username and password credential for https connection.
For downloading files from normal http hosting, we don’t need to implement above function. Now, let us see how to observe the downloading progress.
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite); print(progress); self.progressBar.setProgress(progress, animated: false); }
Here we update the progress bar in main UI thread. Actually, in real use case, we prefer to update the progress bar in a separate thread. To do that, we can replace this code:
self.progressBar.setProgress(progress, animated: false);
with this code:
NSOperationQueue.mainQueue().addOperationWithBlock({ self.progressBar.setProgress(progress, animated: false); });
When a file is downloaded successfully, we can read the content directly or save the file to app data folder. So we implement following function:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { do { //read file data /* let fileHandle:NSFileHandle = try NSFileHandle(forReadingFromURL: location); var fileData:NSData? = nil; fileData = fileHandle.readDataToEndOfFile(); print(fileData); */ //or save file let fileManager:NSFileManager = NSFileManager.defaultManager(); let nsDocumentDirectory:NSSearchPathDirectory = NSSearchPathDirectory.DocumentDirectory let nsUserDomainMask:NSSearchPathDomainMask = NSSearchPathDomainMask.UserDomainMask; let paths:[String] = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true); if (paths.count > 0) { let folderPath:String = String(paths[0]); print(folderPath); let filePath:String = folderPath + "/download_file.data"; let fileURL:NSURL = NSURL.init(fileURLWithPath: filePath); try fileManager.moveItemAtURL(location, toURL: fileURL); } } catch { print(error); } downloadData = nil; }
In above example, I save the downloaded file into folder with a file name “download_file.data”. I also give the example code to read all data from the downloaded file, but comment it.
Pause Download File
Just now I give the example to start download a file and store the downloaded file in app document folder. I think it’s time to show how to pause a downloading task. Actually, it is very simple. But we must remember to store the data in the memory or local file after we pause the download, so that we can resume the download task later.
@IBAction func pauseDownloadSingleFile(sender: AnyObject) { if(downloadTask != nil && isDownload) { downloadTask!.cancelByProducingResumeData({ (resumeData) -> Void in self.downloadData = NSData.init(data: resumeData!); }) isDownload = false; downloadTask = nil; pauseBtn.setTitle("Resume", forState: UIControlState.Normal); } if(!isDownload && downloadData != nil) { downloadTask = urlSession?.downloadTaskWithResumeData(downloadData!); downloadTask!.resume(); isDownload = true; downloadData = nil; pauseBtn.setTitle("Pause", forState: UIControlState.Normal); } }
In this example app, I put a pause button on the stage. Actually, it is a trigger button. It will pause the download task if download is running. Otherwise, it will resume a paused download task. downloadData is a class variable with NSData type. When user click the pause button, I will save downloaded data in to downloadData variable. When I resume the download task, I will pass downloaded data to session to initialize the download task.
Stop Download File
In the last step, I will show how to stop a running download task.
@IBAction func stopDownloadSingleFile(sender: AnyObject) { downloadTask?.cancel(); downloadData = nil; isDownload = false; downloadTask = nil; progressBar.progress = 0; progressLabel.text = "0%"; pauseBtn.setTitle("Pause", forState: UIControlState.Normal); }
Download Manager App 1.0
In this version of Download Manager App, we integrate all download functions in version 1.0. You can check following video to check how the app works:
Get Full Source Code Under $1.99
You can get the whole source code at only $1.99. After you get the source code, you have full permission to use it, modify it and put it in your own project.
I’ve purchased your “Download File in iOS Start Pause and Resume” app and I’m attempting to use it to download a file from my server. It happens to be a http file intead of an https file, but I checked and saw that my info.plist and made sure that Allow Arbitrary Load was set to yes in App Transport Security dictionary. When I run the application and click “start download” the app crashes with the following error, ” Thread 1: EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP, subcode=0x0) ” on line 68 of DownloadViewController.swift. Line 68 says “let url:URL = URL(string:downloalHTTPSURL)!;
How can I correct this issue? Secondly, does this download manager handle all download types? (ie; PDF, JPEG, MP3 or MP4)
Follow up on my use of “Download File in IOS Start Pause and Resume. I dicovered the application didn’t like the form of the URL which it was to download. Once I adjusted to minimize the characters in the filename it started working, but I have another issue. Although I see the progressbar and a count down in the debug window I am recieving the following message ( NSUnderlyingError=0x60000004e820 {Error Domain=NSPOSIXErrorDomain Code=17 “File exists”}} ) although the file is not presently on my machine. I have try this with many files and the same thing happens.
Hello Rebert,
Regarding your error message, I think the problem is that there is an existing file with the same file name. I guess the error is raised by following line:
var fileURL:NSURL = NSURL.init(fileURLWithPath: folderPath);
fileURL = fileURL.URLByAppendingPathComponent("download_file.data", isDirectory: false)
try fileManager.moveItemAtURL(location, toURL: fileURL);
One solution is that, check if the file exist before moving.
Best Regards
James
Hello Robert,
According to the error message you provide, usually that means URL(string:downloadHTTPSURL) getting problem. Please check the variable “downloadHTTPSURL”, which is your url to download. I believe you specify a invalid url string. I think here is a very clear answer for you to refer:
https://stackoverflow.com/questions/28054030/swift-exc-bad-instruction-code-exc-i386-invop-subcode-0x0-with-datataskwith
Best Regards
James
Resume data is not working properly when we pause and resume multiple time. It works fine for the first time but second/ third time it stops working.
What’s the error?
Hi need have to make a demo of app
user can download multiple videos with progress
when user come back again he should be be display status of multiple download videos ,how much a video is downloaded, which get completed,