How to get download progress in AFNetworking 2.0? - ios

I am using AFURLSessionManager to create a new download task:
AFURLSessionManager* manager = ...
NSProgress* p = nil;
NSURLSessionDownloadTask* downloadTask =
[manager downloadTaskWithRequest:request
progress:&p
destination:^NSURL*(NSURL* targetPath, NSURLResponse* response) {...}
completionHandler:^(NSURLResponse* response, NSURL* filePath, NSError* error) {...}
];
[downloadTask resume];
The file gets downloaded fine, however, how do I get progress notifications?
p is always set to nil. I've filed an issue for that.
I've also tried to call setDownloadTaskDidWriteDataBlock on the manager, and I do get progress notifications there but I receive them all grouped together after the file has been downloaded.
Seems like this area is still a bit buggy in AFNetworking 2.0
Any ideas?

You should observe the fractionCompleted property of your NSProgress object using KVO:
NSURL *url = [NSURL URLWithString:#"http://www.hfrmovies.com/TheHobbitDesolationOfSmaug48fps.mp4"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
NSProgress *progress;
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
// …
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
[progress removeObserver:self forKeyPath:#"fractionCompleted" context:NULL];
// …
}];
[downloadTask resume];
[progress addObserver:self
forKeyPath:#"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
Then add the observer method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"fractionCompleted"]) {
NSProgress *progress = (NSProgress *)object;
NSLog(#"Progress… %f", progress.fractionCompleted);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Of course, you should check keyPath and/or object parameters to decide if that's the object/property you want to observe.
You can also use the setDownloadTaskDidWriteDataBlock: method from AFURLSessionManager (from which AFHTTPSessionManager inherits) to set a block for receiving download progress updates.
[session setDownloadTaskDidWriteDataBlock:^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
NSLog(#"Progress… %lld", totalBytesWritten);
}];
This AFNetworking method maps the URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: method from NSURLSessionDownloadDelegate protocol to a more convenient block mechanism.
BTW, Apple's KVO implementation is severely broken. I recommend using a better implementation like the one proposed by Mike Ash with MAKVONotificationCenter. If you are interested in reading why Apple's KVO is broken, read Key-Value Observing Done Right by Mike Ash.

I faced a similar problem, and found a solution.
Check the link below:
http://cocoadocs.org/docsets/AFNetworking/2.0.1/Categories/UIProgressView+AFNetworking.html
#import <AFNetworking/UIKit+AFNetworking.h>
and use the additional method available to your UIProgressView
setProgressWithDownloadProgressOfTask:animated:
How I did it:
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response){
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[targetPath lastPathComponent]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error){
NSLog(#"File downloaded to: %#", filePath);
}];
[self.progressView setProgressWithDownloadProgressOfTask:downloadTask animated:YES];
[downloadTask resume];

Simple solutions for Swift:
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let sessionManager = AFURLSessionManager(sessionConfiguration: sessionConfiguration)
let request = NSURLRequest(URL: url)
let sessionDownloadTask = sessionManager.downloadTaskWithRequest(request, progress: nil, destination: { (url, response) -> NSURL in
return destinationPath.URLByAppendingPathComponent(fileName) //this is destinationPath for downloaded file
}, completionHandler: { response, url, error in
//do sth when it finishes
})
Now you have 2 options:
Using UIProgressView and setProgressWithDownloadProgressOfTask:
progressView.setProgressWithDownloadProgressOfTask(sessionDownloadTask, animated: true)
Using AFURLSessionManager and setDownloadTaskDidWriteDataBlock:
sessionManager.setDownloadTaskDidWriteDataBlock { session, sessionDownloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
let progress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
//do sth with current progress
}
At the end do not forget about:
sessionDownloadTask.resume()

For Download file with progress status use this code
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://..."];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress)
{
NSLog(#"Progress: %f", downloadProgress.fractionCompleted);
if (progressBlock) {
progressBlock(downloadProgress);
}
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
if (response && successBlock) {
successBlock(response,filePath);
}
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];

Related

How do I download large file with streaming in AFNetworking 3

I want to download large file using AFNetworking 3. But I need to resume download while there is any interruption like internet loss or any other things. If any interrupts while downloading I want to start download from where it stop early. Is it possible using AFNetworking or do I use any other libraries? Please anybody help me.
Here is my code.
NSURLRequest *request = [NSURLRequest requestWithURL:formattedURL];
//Watch the manager to see how much of the file it's downloaded
[manager setDownloadTaskDidWriteDataBlock:^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
//Convert totalBytesWritten and totalBytesExpectedToWrite into floats so that percentageCompleted doesn't get rounded to the nearest integer
CGFloat written = totalBytesWritten;
CGFloat total = totalBytesExpectedToWrite;
CGFloat percentageCompleted = written/total;
//Return the completed progress so we can display it somewhere else in app
//progressBlock(percentageCompleted);
NSLog(#"Percentage Completed : %f",percentageCompleted);
[self updateProgressBar:percentageCompleted];
}];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
//Getting the path of the document directory
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
NSURL *fullURL = [documentsDirectoryURL URLByAppendingPathComponent:#"3511_1464694276.zip"];
//If we already have a video file saved, remove it from the phone
return fullURL;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (!error) {
//If there's no error, return the completion block
//completionBlock(filePath);
} else {
//Otherwise return the error block
//errorBlock(error);
}
}];
[downloadTask resume];
Read more about it here : https://github.com/AFNetworking/AFNetworking
And try to ask questions when you have written some code or tried something on your own.
Download file with progress :
- (IBAction)downloadAudio:(id)sender {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://101songs.com/fileDownload/Songs/0/26958.mp3"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
dispatch_async(dispatch_get_main_queue(), ^{
//Update the progress view
[_myProgressView setProgress:downloadProgress.fractionCompleted];
});
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
// Do operation after download is complete
}];
[downloadTask resume];
}

Showing spinner using spinKit in iOS while fetching JSON

What I am trying to do is, on a click of a button I am fetching data using AFNetworking pod. What i want is after fetching data I want to display NSLog(#"/n /nAfter fetching"); but it is coming before the json data.
Here is the code I have written in IBAction of Button.
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
//Load the json on another thread
NSURL *url = [NSURL URLWithString:finalURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response,id responseObject, NSError *error) {
if (error) {
NSLog(#"\n Error --> %#",error);
}
else{
jsonDictionary = (NSDictionary*) responseObject;
NSLog(#"/n Data :: %#",jsonDictionary);
}
}];
[dataTask resume];
});
NSLog(#"/n /nAfter fetching");
Please provide a nice way of doing it and do correct me If I have gone wrong with it. Thank you.
As AFURLSessionManager makes the Asynchronous operation. YOu will get the data in completion
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
//Load the json on another thread
[self startSpinner];
NSURL *url = [NSURL URLWithString:finalURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response,id responseObject, NSError *error) {
[self stopSpinner];
if (error) {
NSLog(#"\n Error --> %#",error);
}
else{
NSLog(#"/n /nAfter fetching"); //this is where you receive data
jsonDictionary = (NSDictionary*) responseObject;
NSLog(#"/n Data :: %#",jsonDictionary);
}
}];
[dataTask resume];
});
if you want to make synchronous operation using URLSession below method will help you
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
returningResponse:(__autoreleasing NSURLResponse **)responsePtr
error:(__autoreleasing NSError **)errorPtr {
dispatch_semaphore_t sem;
__block NSData * result;
result = nil;
sem = dispatch_semaphore_create(0);
[[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (errorPtr != NULL) {
*errorPtr = error;
}
if (responsePtr != NULL) {
*responsePtr = response;
}
if (error == nil) {
result = data;
}
dispatch_semaphore_signal(sem);
}] resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return result;
}

Get MBProgressHUD's progress from NSURL

I am trying to calculate the progress of my download method and show MBProgressHUD's progress during file is downloading , but I don't know how to calculate the progress float ! here is my code :
- (IBAction)preview:(id)sender {
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
// Set determinate mode
HUD.mode = MBProgressHUDModeAnnularDeterminate;
HUD.delegate = self;
HUD.labelText = #"Loading";
// myProgressTask uses the HUD instance to update progress
[HUD showWhileExecuting:#selector(downloadDataFromMac) onTarget:self withObject:nil animated:YES];
}
- (void)downloadData {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:pathWithData];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
//Hide HUD
[HUD hide:YES];
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:filePath];
[self.documentInteractionController setDelegate:self];
[self.documentInteractionController presentPreviewAnimated:YES];
}];
[downloadTask resume];
}
EDITED :
- (void)downloadDataFromMac {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:pathWithData];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSProgress *progress;
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
[progress addObserver:self
forKeyPath:#"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
//Hide HUD
[HUD hide:YES];
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:filePath];
[self.documentInteractionController setDelegate:self];
[self.documentInteractionController presentPreviewAnimated:YES];
}];
[downloadTask resume];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"fractionCompleted"]) {
NSProgress *progress = (NSProgress *)object;
NSLog(#"Progress… %f", progress.fractionCompleted);
//do something with your progress here, for eg :
//but dont forget to first make HUD a class property so you can update it
[HUD setProgress:progress.fractionCompleted];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
You can use RSNetworkKit. It has all handy methods for all network related calls, download and upload files with progress. It has internally implemented AFNetworking.
https://github.com/rushisangani/RSNetworkKit
RSDownlaodManager has a method to download any file with progress
you can simple use like this.
[[RSDownloadManager sharedManager] downloadWithURL:#"URLString" downloadProgress:^(NSNumber *progress) {
// show progress using HUD here
// must use main thread to show progress or update UI.
} success:^(NSURLResponse *response, NSURL *filePath) {
} andFailure:^(NSError *error) {
}];
You can add an NSProgress property to the NSURLSessionDownloadTask definition, and then you can observe that property using KVO. So just before you create the download task, create the property and add it to the definition, like this :
NSProgress *progress;
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
//Hide HUD
[HUD hide:YES];
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:filePath];
[self.documentInteractionController setDelegate:self];
[self.documentInteractionController presentPreviewAnimated:YES];
}];
[progress addObserver:self
forKeyPath:#"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
[downloadTask resume];
Then to observe that progress property as it changes, add this method to your class :
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"fractionCompleted"]) {
NSProgress *progress = (NSProgress *)object;
NSLog(#"Progress… %f", progress.fractionCompleted);
//do something with your progress here, for eg :
//but dont forget to first make HUD a class property so you can update it
[self.hud setProgress:progress.fractionCompleted];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

NSURLSessionDownloadTask and retrying

I am using the following code to download a file from the web. How do i structure the code (using blocks) such that, if it fails, it will be retried with a max of retrycount. Here is the code that i am using to download the file.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *myURL = [NSURL URLWithString:#"urlstring"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL];
[request setValue:token forHTTPHeaderField:#"Authorization"];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *directoryURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
return [directoryURL URLByAppendingPathComponent:fileName];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
//check here if error then retry using block ?
if (!error)
{
//do something with the file
}
else
{
//retry download ??
}
}
}];
[downloadTask resume];
You could create a retryCount instance variable and set it to however many times you want to retry the network call. Then you could put all your networking code in a method with a custom completion handler. Your completion handler could be a block that takes a BOOL as a parameter and returns void. Then, in your networking code, create a local BOOL variable and set it to YES or NO based on whether the network call succeeded or failed. You'll run the completion handler argument within your networking code. Then the block (completion handler) that you send as a parameter to the method can "see" whether the local variable in your method was set to YES or NO. If it was YES, the network call succeeded, so do nothing. If it was NO, the network call failed, so check retryCount--if it's > 0, call the networking method again and decrement retryCount.
UPDATE
This should give you a rough idea...
typedef void (^CustomCompletionHandler)(BOOL); // Create your CustomCompletionHandler type
- (void)getDataAndCallCompletionHandler:(CustomCompletionHandler *)completionHandler {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *myURL = [NSURL URLWithString:#"urlstring"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL];
[request setValue:token forHTTPHeaderField:#"Authorization"];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
BOOL success = NO; // Create local variable to keep track of whether network call succeeded or failed
NSURL *directoryURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
return [directoryURL URLByAppendingPathComponent:fileName];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
//check here if error then retry using block ?
if (!error)
{
success = YES;
//do something with the file
}
else
{
success = NO;
//retry download ??
}
}
// Get main queue and call completionHandler and pass success:
// completionHandler(success)
}];
[downloadTask resume];
}
You would call this method by doing something like...
[self getDataAndCallCompletionHandler:^(BOOL success) {
// If success == true {
// Do whatever you need to
// } else {
// Check retryCount
// If you need to, call getDataAndCallCompletionHandler: again
// }
}];

How to save a file using NSURLSessionDownloadTask and AFNetworking 2?

I'm creating a download task as indicated in the AFNetworking docs (https://github.com/AFNetworking/AFNetworking#creating-a-download-task).
When the NSURLSessionDownloadTask reached the block completionHandler the error is null, so there's no error. Also, the filePath returns a valid NSURL, such as /var/mobile/Applications/id-of-the-app/Documents/section/section13.png.
The problem is that it's not saving the file in the specified folder (the folder exists, and the file to be downloaded too).
Any ideas? Here's the code:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:fileUrl]];
NSString *filename = [NSString stringWithFormat:#"section%#.%#", section.sectionId, [request.URL pathExtension]];
NSURL *saveUrl = [FilesManager urlForResourceType:ResourceTypeSection andFilename:filename];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request
progress:nil
destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
return saveUrl;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"path - %#", filePath);
if (!error) {
NSLog(#"we're good");
}
}];
[downloadTask resume];

Resources