AFNetworking 2.0 track file upload progress
The interface of AFHTTPSession
doesn't provide a method to set a progress block. Instead, you'll have to do the following:
// 1. Create `AFHTTPRequestSerializer` which will create your request.
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
// 2. Create an `NSMutableURLRequest`.
NSMutableURLRequest *request =
[serializer multipartFormRequestWithMethod:@"POST" URLString:@"http://www.myurl.com"
parameters:dataToPost
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:imageData
name:@"attachment"
fileName:@"myimage.jpg"
mimeType:@"image/jpeg"];
}];
// 3. Create and use `AFHTTPRequestOperationManager` to create an `AFHTTPRequestOperation` from the `NSMutableURLRequest` that we just created.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFHTTPRequestOperation *operation =
[manager HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Failure %@", error.description);
}];
// 4. Set the progress block of the operation.
[operation setUploadProgressBlock:^(NSUInteger __unused bytesWritten,
long long totalBytesWritten,
long long totalBytesExpectedToWrite) {
NSLog(@"Wrote %lld/%lld", totalBytesWritten, totalBytesExpectedToWrite);
}];
// 5. Begin!
[operation start];
In addition, you don't have to read the image via UIImage
and then compress it again using JPEG to get an NSData
. Just use +[NSData dataWithContentsOfFile:]
to read the file directly from your bundle.
AFNetworking 2.0 + UIProgressView how to correctly display progress for an upload session
Googleing, githubbing, I've found the answer, essentially this is a "feature" of NSURLSession, when you create a session with uploadstream the task takes over on your setted header. Since in a stream there is not a really content size, if we set something the task will rewrite it.
Fortunately NSURLSession has two request properties currentRequest
(the overriden one) and the originalRequest
. The original request will continue to keep our Content-Length
.
I made few mods to the UIProgressView+AFNetworking category to make it works using the set content length, it would be better another check to see if there is a stream.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(__unused NSDictionary *)change
context:(void *)context
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
//upload content length issue
// if ([object countOfBytesExpectedToSend] > 0) {
NSInteger byteToSend = [[[object originalRequest] valueForHTTPHeaderField:@"Content-Length"] integerValue];
if (byteToSend){
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesSent] / (byteToSend * 1.0f) animated:self.af_uploadProgressAnimated];
// [self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
});
}
}
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
if ([object countOfBytesExpectedToReceive] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated];
});
}
}
if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
@try {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];
if (context == AFTaskCountOfBytesSentContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
}
if (context == AFTaskCountOfBytesReceivedContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
}
}
@catch (NSException * __unused exception) {}
}
}
}
#endif
}
AFNetworking Upload multiple files in sequence
As I understand, you want to upload image one by one. After first image is uploaded, start upload next image.
In my opinion, you can use recursive in this case. Take a look at my code below.
// Use recursive to upload an array items
- (void)startUploadItems:(NSMutableArray*)items {
if (items.count < 1) {
return;
}
[self startUploadItem:items[0] completion:^(BOOL success) {
[items removeObjectAtIndex:0];
[self startUploadItems:items];
}];
}
// Use to upload a single item.
- (void)startUploadItem:(id)item completion:(void(^)(BOOL success))completion {
NSLog(@"item %@", item);
NSData *imageData = UIImagePNGRepresentation(item);
NSString *urlUpload = @"https://domain/api/wp-json/wp/v2/media?access_token=";
urlUpload = [urlUpload stringByAppendingString:[Lockbox unarchiveObjectForKey:@"access_token"]];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.label.text = @"Uploaded photo";
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:urlUpload parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:imageData name:@"file" fileName:@"filename.png" mimeType:@"image/png"];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionUploadTask *uploadTask;
uploadTask = [manager
uploadTaskWithStreamedRequest:request
progress:^(NSProgress * _Nonnull uploadProgress) {
dispatch_async(dispatch_get_main_queue(), ^{
hud.progress = uploadProgress.fractionCompleted;
});
}
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
[hud hideAnimated:YES];
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
if ([responseObject objectForKey:@"id"] == nil ||
[[responseObject objectForKey:@"id"] isEqual:[NSNull null]] ||
[responseObject objectForKey:@"id"] == [NSNull null]) {
NSLog(@"NO ID %@", responseObject);
} else {
NSLog(@"ID: %@", [responseObject objectForKey:@"id"]);
NSLog(@"source_url: %@", [responseObject objectForKey:@"source_url"]);
}
}
if (completion) {
completion(!error);
}
}];
[uploadTask resume];
}
Usage:
[self startUploadItems:imagesArray];
Related Topics
Launch a Local Notification at a Specific Time in iOS
Programmatically Linking Uipagecontrol to Uiscrollview
Popping and Pushing View Controllers in Same Action
Xcode 8, iOS 10 - "Starting Webfilter Logging for Process"
Custom Cell Reorder Behavior in Collectionview
How to Update Uibutton Title/Text Programmatically
Custom Font in iOS Not Working
Filenames Are Used to Distinguish Private Declarations of the Same Name' Error
Could Not Instantiate Class Named Ibnslayoutconstraint
Core Bluetooth - Constant Rssi Updates of In-Range Devices
In-App Purchase in Swift with a Single Product
How to Lock Portrait Orientation for Only Main View Using Swift
Change Order of Read Items with Voiceover
Using Enum as Property of Realm Model
Does iOS Provide Built in Text to Speech Support or Any Class Like Nsspeechrecognizer
Uitableview Backgroundcolor Always White on iPad
How to Change the Highlighted Color of a Uibutton
Why the Extension Is "Momd" But Not "Xcdatamodel" When Search the Path for the Model File