How to get download progress in AFNetworking 2.0?
Asked Answered
J

4

25

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?

Jard answered 2/10, 2013 at 19:19 Comment(2)
Agreed. I'm not able to get the downloader to even work. It cancels immediately.Muggy
If you want to be iOS6/7 compliant, you should use AFHTTPRequestOperation. AFURLSessionManager only works on iOS 7. I've posted an answer showing how to get download progress using AFHTTPRequestOperation.Titlark
A
45

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.

Antonioantonius answered 15/10, 2013 at 12:7 Comment(9)
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFBackgroundDownloadTask fractionCompleted]: unrecognized selector sent to instance 0xd888b40'Alyworth
The fractionCompleted property belongs to the NSProgress object.Antonioantonius
Be aware that in KVO you should not call super if you handle the notification.Avrom
Thanks @Avrom . I've just edited it. BTW, I recommend a better implementation of KVO like the one proposed by Mike Ash: github.com/mikeash/MAKVONotificationCenter.Antonioantonius
In general, you might use the pattern NSStringFromSelector(@selector(fractionCompleted)) instead of using compiler opaque strings like @"fractionCompleted".Athos
missed typed NSUR should be NSURL also there is an error for "control reaches end of non-void block"Ball
How to add extra headers to AFHTTPSessionManager. Error Domain=NSURLErrorDomain Code=-1102 "You do not have permission to access the requested resource." UserInfo=0xb96ba30 NSLocalizedDescription=You do not have permission to access the requested resource. This is how i am doing [self.sessionManager.requestSerializer setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; [self.sessionManager.requestSerializer setValue:[NSString stringWithFormat:@"Bearer %@",authToken] forHTTPHeaderField:@"Authorization"]; but when i check the request header it shows as (null)Blacksmith
Note: You'll need to use setTaskDidSendBodyDataBlock for data tasks (instead of the setDownloadTaskDidWriteDataBlock mentioned in the answer).Kary
@CameronLowellPalmer What is the advantage of generating the string at runtime? Except of the cases, where it is not possible to do so.Ranite
M
18

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];
Measurement answered 26/10, 2013 at 15:28 Comment(2)
Might be helpful to note that the manager variable in this code snippet is an instance of AFURLSessionManager.Evergreen
Awesome example! Thanks!Dastard
M
0

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:

  1. Using UIProgressView and setProgressWithDownloadProgressOfTask:

    progressView.setProgressWithDownloadProgressOfTask(sessionDownloadTask, animated: true)
    
  2. 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()
Mcgaw answered 9/12, 2015 at 8:14 Comment(0)
D
0

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];
Deadly answered 21/3, 2017 at 7:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.