Update: The Swift version of this tutorial is available here.
Since iOS7, Apple engineers introduced a new set of networking API called NSURLSession, in order to help with downloading/uploading content via HTTP or HTTPS protocols.
NSURLSession came with a lot of improved tasks which developers had to write a lot of code to handle. These tasks are:
- Manage downloads when the app is in a background state.
- Offers a download session configuration object to store data in a file and continue the download task even when the app crashes or get suspended.
- Provide capabilities to pause, resume, cancel, restart the download task.
- Provide a way to notify the app about the download progress via its custom delegate object using a set of protocol methods.
- Provide the ability to resume suspended, canceled or failed downloads where they left off.
In this tutorial, you will learn how to download a PDF file using NSURLSession. You will be experiencing the common tasks like pause, resume, cancel and restart the download along the way. After downloading the file, you will visualize it using UIDocumentInteractionController.
So, let’s get this done 🙂
For this tutorial, I will be using Xcode 6, but if you are using Xcode 5, that should be fine since the NSURLSession API classes doesn’t get changed in the iOS SDK8.
Open up Xcode, create a new project. Choose the Single View Application template and click next.
In the next screen, give your app a name (PDFDownloader for example) and make sure the chosen Language is Objective-C from the drop down list.
Click next and then choose a location where to save your project and click ‘Create’ button.
Before coding, let’s add the basic controls for your project. Select Main.storyboard from the project navigator where all your project files are listed in the left pane.
You may notice the view controller bounds are larger than what you use to see in Xcode 5 and earlier. Xcode 6 set this size frames by default, so you need to adjust them manually to the iPhone screen size before you continue.
To do that, select the view controller, and from the File Inspector, make sure ‘Use Size Classes’ is UNchecked.
From the prompt, make sure you select ‘iPhone’ from the drop down list in order to get the view controller an iPhone frames. Then click the ‘Disable Size Classes’ button.
You will notice the view controller frames are now the same as the iPhone frames. Now, you can comfortably continue your work 🙂
As I said, you will be downloading a PDF file from a HTTP source. So let’s add a button which will start the download process for you, and three other buttons to respectively pause, resume and cancel the download.
From the object library, drag a UIButton to the view and repeat the same step three more times to bring in three more buttons. Customize the look of the buttons the way you want.
Next, drag a UIProgressView from the Object Library to the view. The progress view will be useful to show you the progress of the download task as bytes are being downloaded.
Your view will look something like this:
Ok, now it’s time to code. You need to add action methods for the buttons and also you need to bind the UIProgressView to an outlet in order to control it from within your code.
Let’s define actions for the buttons. Select Main.storyboard and then switch to the ‘Assistant editor’.
Then, ctrl click on the ‘Download’ button and drag a line from the button to the ViewController.m which get shown on the right view after you switched to the Assistant editor.
Release the mouse click, a popup will show up to setup your method. Give the action method a name like ‘downloadFile’ and make sure the defined event is ‘Touch Up Inside’ like the image above, then click Connect.
Now repeat the same step with the remaining buttons. Name the buttons actions: pauseDownload, resumeDownload, cancelDownload respectively.
Last thing before you go for coding, you need to add the outlet connection for the UIProgressView. Ctrl click on the UIProgressView and drag a line in between the @interface and @end block in the ViewController.m file.
Name it progressView and click Connect. After connecting the actions and outlet, your file ViewController.m should look like this:
That’s all for the UI, now time for code 😉
Switch to the Standard editor and select the ViewController.m file.
Add an NSURLSessionDownloadTask variable between the curly brackets like this:
[sourcecode lang=”objc”]
@interface ViewController (){
NSURLSessionDownloadTask *download;
}[/sourcecode]
After the closing bracket, add a property for the background session you will be using to download the task.
[sourcecode lang=”objc”]
@property (nonatomic, strong)NSURLSession *backgroundSession;
[/sourcecode]
The @interface block in the ViewController.m file should look like this:
[sourcecode lang=”objc”]
#import "ViewController.h"
@interface ViewController (){
NSURLSessionDownloadTask *download;
}
@property (nonatomic, strong)NSURLSession *backgroundSession;
@property (strong, nonatomic) IBOutlet UIProgressView *progressView;
@end
[/sourcecode]
You will be running a download task for the sake of this tutorial, that’s why you declared an NSURLSessionDownloadTask object. And the backgroundSession property will be used to initialize the download object (NSURLSessionDownloadTask) in order to start the download task.
Move to viewDidLoad method and right after [super viewDidLoad], write the following code:
[sourcecode lang=”objc”]
// 1
NSURLSessionConfiguration *backgroundConfigurationObject = [NSURLSessionConfiguration backgroundSessionConfiguration:@"myBackgroundSessionIdentifier"];
// 2
self.backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfigurationObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
// 3
[self.progressView setProgress:0 animated:NO];
[/sourcecode]
Let’s explain the above code:
1/ NSURLSession API has 3 types of sessions: default, ephemeral and download sessions. The behavior of a session is determined by the configuration object used to create it. In this tutorial, you are going to use a background configured session because the download task of the file should be done in the background thread.
2/ Then you create the NSURLSession object using the configuration object created above. The delegateQueue argument is a queue for scheduling the delegate calls and completion handlers which get called by the delegate object (self).
3/ Here you reset the progress property of progressView to 0 because at that time the download doesn’t start yet.
When the download task starts, the NSURLSessionDownloadTask object will throw events, for example, the object will notify the delegate about how many bytes are downloaded and how many bytes are left. The delegate should implement some protocol methods in order to take actions when these kind of events occurs.
In ViewController.m file, right before @end, paste the following code:
[sourcecode lang=”objc”]
// 1
– (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectoryPath = [paths objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *destinationURL = [NSURL fileURLWithPath:[documentDirectoryPath stringByAppendingPathComponent:@"file.pdf"]];
NSError *error = nil;
if ([fileManager fileExistsAtPath:[destinationURL path]]){
[fileManager replaceItemAtURL:destinationURL withItemAtURL:destinationURL backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:nil error:&error];
[self showFile:[destinationURL path]];
}else{
if ([fileManager moveItemAtURL:location toURL:destinationURL error:&error]) {
[self showFile:[destinationURL path]];
}else{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"PDFDownloader" message:[NSString stringWithFormat:@"An error has occurred when moving the file: %@",[error localizedDescription]] delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
[alert show];
}
}
}
// 2
– (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
[self.progressView setProgress:(double)totalBytesWritten/(double)totalBytesExpectedToWrite
animated:YES];
}
// 3
– (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"PDFDownloader" message:@"Download is resumed successfully" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
[alert show];
}
[/sourcecode]
The above delegate methods are important to handle common events, let’s explain them quickly:
1/ didFinishDownloadingToURL delegate method provides the app with the URL to a temporary file where the downloaded content is stored, so you need to move your content to a permanent location. That’s what you did above, you tried to move your file to a path in the document directory in your device. If the path already exist, then you replace the file with the new path (if you don’t do that, an error will raise because the system cannot override a file automatically in case there is a path duplication).
2/ downloadTask:didWriteData delegate method provides the app with status informations about the progress of the download, you use this delegate method to increase the progress property of the progress view so that it always reflect how much time is left for the task to finish. You get the progress value by dividing the total bytes written by the total bytes expected to write.
3/ downloadTask:didResumeAtOffset delegate method is called when its attempt to resume a previously failed download was successful.
Next, you should implement one more delegate method which get called when the download task is completed. Paste the following method:
[sourcecode lang=”objc”]
– (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
download = nil;
[self.progressView setProgress:0];
if (error) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"PDFDownloader" message:[error localizedDescription] delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
[alert show];
}
}
[/sourcecode]
Nothing complicated above, since your download is completed, you need to set the download variable to nil because you don’t need it anymore, and reset the progress view to 0.
Note that URLSession:task:didCompleteWithError is an NSURLSessionTaskDelegate protocol method, this is a general protocol method which is useful for all types of tasks (download, upload, etc).
Ok, now that you managed all required and useful NSURLSession related protocol methods, you need to handle the case when you complete downloading the file. At that time, you will open the PDF file to visualize it, so let’s go ahead to implement the showFile method.
Paste the following code in ViewController.m file:
[sourcecode lang=”objc”]
– (void)showFile:(NSString*)path{
// Check if the file exists
BOOL isFound = [[NSFileManager defaultManager] fileExistsAtPath:path];
if (isFound) {
UIDocumentInteractionController *viewer = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]];
viewer.delegate = self;
[viewer presentPreviewAnimated:YES];
}
}
[/sourcecode]
If the file exist at the given path, you open it using UIDocumentInteractionController. Pretty simple 🙂
In order to use presentPreviewAnimated method, you should implement the protocol method documentInteractionControllerViewControllerForPreview, so go ahead and add its implementation.
[sourcecode lang=”objc”]
– (UIViewController *) documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller{
return self;
}
[/sourcecode]
Now, you should implement the buttons action implementations, no worries, they are few lines of code and you should be set.
Always in the ViewController.m file, add the following code:
[sourcecode lang=”objc”]
– (IBAction)downloadFile:(id)sender {
if (nil == download){
NSURL *url = [NSURL URLWithString:@"http://www.nbb.be/DOC/BA/PDF7MB/2010/201000200051_1.PDF"];
download = [self.backgroundSession downloadTaskWithURL:url];
[download resume];
}
}
– (IBAction)pauseDownload:(id)sender {
if (nil != download){
[download suspend];
}
}
– (IBAction)resumeDownload:(id)sender {
if (nil != download){
[download resume];
}
}
– (IBAction)cancelDownload:(id)sender {
if (nil != download){
[download cancel];
}
}
[/sourcecode]
In downloadFile method, you used the backgroundSession property to initialize a new download session task. Then you call resume to start the task.
The rest of action methods are self explanatory, they shouldn’t be hard for you 😉
Now you are set and ready to run your program, but there is a small thing missing. You need to let your controller conform to the NSURLSessionDownloadDelegate and UIDocumentInteractionControllerDelegate protocols. For that, switch to ViewController.h and change it to look like the following:
[sourcecode lang=”objc”]
@interface ViewController : UIViewController <NSURLSessionDownloadDelegate, UIDocumentInteractionControllerDelegate>
@end
[/sourcecode]
That’s it, all is ready to go. Run your app and click the Download button, feel free to pause and resume the download.
After the download is finished, the PDF file will be presented in a modal controller.
As usual, you can download the running project here.
Hope you enjoyed this tutorial. More tutorials are in the queue for you 🙂
Fee free to leave a comment, I am looking forward to hearing from you!
Also, in response to your emails, I wrote a Swift 2 version of this tutorial for you here. You are welcome 🙂
Discover more from SweetTutos
Subscribe to get the latest posts sent to your email.