AFNetworking setImageWithURLRequest download progress - iphone

I am using this code to set image to UIImageView.
NSURLRequest *URLRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:imageToLoad]];
[imageView setImageWithURLRequest:URLRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[cell.image setImage:image];
[cell.activityIndicator stopAnimating];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
// Nothing
}];
But I want to track download progress with that method, is it possbile to do it in setImageWithURLRequest method?
Normally I do this to show loading progress percentage:
[SVProgressHUD showWithStatus:#"Start download..."];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:link]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// success
[SVProgressHUD showSuccessWithStatus:#"Done."];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failed
[SVProgressHUD showErrorWithStatus:#"Failed."];
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
[SVProgressHUD showWithStatus:[NSString stringWithFormat:#"Downloading... %0.0f%%", totalBytesRead*100*1.0/(totalBytesRead+totalBytesExpectedToRead)]];
}];

Out of the box, no UIImageView+AFNetworking category doesn't have this functionality. However, it can easily be added to by adding this method to the category:
-(void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block{
[self.af_imageRequestOperation setDownloadProgressBlock:block];
}

Take a look at this cocoa pod: https://github.com/xmartlabs/XLRemoteImageView . It uses objective-c internals to achieve what you want. I hope it helps you.

Related

AFNetworking with AFJSONRequestOperation and filedata never completes

I'm using AFNetworking to post json data to my webservice and get a json response back. However now I also want to add multipart formdata to these POST's. If I do this (even without the parameters I added first) the completion block never fires. The progress block DOES fire and I can see that the file uploads correctly.
Does anybody have any experience posting images like this with AFNetworking? I am using the latest AFNetworking source / version.
This is my initial code for posting a dictionary with json in it. This works fine.
NSMutableDictionary *postdata = [[NSMutableDictionary alloc] init];
[postdata setObject:[postdictionary JSONString] forKey:#"request"];
NSMutableURLRequest *jsonRequest = [httpClient requestWithMethod:#"POST"
path:path
parameters:postdata];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:jsonRequest success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {// Success} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {// Failure }];
[operation start];
This is the version of the code for submitting an image (doesn't work)
NSMutableURLRequest *jsonRequest jsonRequest = [httpClient multipartFormRequestWithMethod:#"POST" path:path parameters:postdata constructingBodyWithBlock: ^(id <AFMultipartFormData> formData) {
[formData appendPartWithFileData:imageData name:#"photo" fileName:#"photo.jpg" mimeType:#"image/jpeg"];
[formData throttleBandwidthWithPacketSize:5000 delay:0.1];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:jsonRequest];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Upload succes");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Upload failed");
}];
[operation start];
I use this code on a number of apps and havent had any problems with it (note I am using the AFJSONRequestOperation and AFRestClient)
set the request
// I have a shared singleton in a separate file APIClient.h
AFRESTClient *httpClient = [APIClient sharedClient];
NSURLRequest *request = [httpClient multipartFormRequestWithMethod:#"POST" path:method parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData> formData)
{[formData appendPartWithFileData:imageData
name:#"image"
fileName:#"image.png"
mimeType:#"image/png"];
}];
then the operation,
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request];
my progress block
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
CGFloat progress = ((CGFloat)totalBytesWritten) / totalBytesExpectedToWrite * 100;
[SVProgressHUD showProgress:50 status:[NSString stringWithFormat:#"%0.1f %%", progress]];
}];
success block (my API returns a stat -ok or fail, then the data (same as flickr API)
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"response string: %#", operation.responseString);
NSString *resultStat = [response objectForKey:#"stat"];
if ([resultStat isEqualToString:#"ok"]) {
//success
// do something with your data that is returned from the API
} else {
//error
NSLog(#"Data Error: %#", response);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [error localizedDescription]);
}];
[httpClient enqueueHTTPRequestOperation:operation];
cannot see any issues with your code, few possibilities:
try setting short timeout interval and check if request completes
[request setTimeoutInterval:5];
also try commenting out the throttling line and see if it changes anything

iOS - Performing multiple AFJSONRequestOperations that write to Core Data in background

I am having trouble determining the best way to manage updates to my apps SQLite database (using Core Data)
When my app launches, it hits a server to determine which tables need updating. I then do a service call for each of those tables. Once I get the JSON back for each, it creates/updates the corresponding objects in my SQLite DB.
What I am doing works, as it performs each request and updates each table that needs to be-- but I don't think I am doing this correctly.
Doing this still locks my UI Thread and I need to be able to run this code asynchronously in the background every 10 minutes or so.
AFJSONRequestOperation* operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSMutableArray *operations = [NSMutableArray new];
//for each of the tables that need updating, create a AFJSONRequestOperation
for(NSString *str in [JSON valueForKey:#"Views"])
{
NSString* path = [NSString stringWithFormat:#"cache/%#/?deviceUID=%#&token=%#", str, #"00000-00000-0000-00001", [_globals getToken]];
NSURLRequest* request = [client requestWithMethod:#"GET" path:path parameters:nil];
AFJSONRequestOperation* operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self updateTable:str withJSON:JSON];
}
failure:nil];
[operations addObject:operation2];
}
//AFHTTPClient
[client enqueueBatchOfHTTPRequestOperations:operations progressBlock:nil completionBlock:^(NSArray *operations) {
//this gets called way before the objects are done updating to the DB
NSLog(#"DONE ALL REQUESTS");
[_HUD hide:YES]; // getting called after getting all of the JSON not after all tables are updated
}];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
[_HUD hide:YES];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Failed" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}];
[operation start];
Heres my updateTable function with only 1 condition
- (void)updateTable:(NSString *)tblName withJSON:(id)JSON
{
for(NSDictionary *record in [JSON valueForKey:#"Records"])
{
NSString *viewName = [[record valueForKey:#"ViewName"] lowercaseString];
//Worker
if([viewName isEqualToString:[[NSString stringWithFormat:#"Worker_vw_iSales"] lowercaseString]])
{
if([Worker doesWorkerExist:[record valueForKey:#"JSONData"]])
{
NSLog(#"deleting old worker");
[ad.managedObjectContext deleteObject:[Worker doesWorkerExist:[record valueForKey:#"JSONData"]]];
}
NSEntityDescription *desc = [NSEntityDescription entityForName:NSStringFromClass([Worker class]) inManagedObjectContext:ad.managedObjectContext];
Worker *worker = [[Worker alloc] initWithEntity:desc insertIntoManagedObjectContext:ad.managedObjectContext];
[worker initWithJSONSting:[record valueForKey:#"JSONData"]];
NSLog(#"Creating Worker: %#", worker.firstName);
}
}
}
I hope this is not all too confusing-- if so I can try to explain more.
I may be doing this completely wrong, and if I am just let me know. I have tried a few other things, including using an NSOperationQueue instead of AFHHTTPs enqueueBatchOfHTTPRequestOperations:requests but I cannot get the behavior I am looking for.
Thanks!
What you are looking for now is setSuccessCallbackQueue: on AFJSONRequestOperation. AFNetworking set all their success blocks to run on the main queue unless otherwise specified.
What I've done is
#implementation myClass {
dispatch_queue_t backgroundQueue;
}
- (id)init
{
if (self = [super init]){
backgroundQueue = dispatch_queue_create("com.proj.myClass", 0);
}
return self;
}
- (void)doSomeStuff
{
NSMutableURLRequest *request = [[myAFAPIClient sharedClient] requestWithMethod:#"GET" path:path parameters:params];
AFJSONRequestOperation *myOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//Success Block
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
[myOperation setSuccessCallbackQueue:backgroundQueue];
[[myAFAPIClient sharedClient].operationQueue addOperation:myOperation];
}
So the different would be you are enqueue'ing operations, where I'm just adding them straight to the client.
Also, where I have //Success Block I do all sorts of things such as dispatching other methods onto backgroundQueue, which those methods make more JSON requests and I don't have any issues.

AFNetworking not returning data synchronously inside a block

Can't receive JSON synchronously inside a block using AFNetworking. I checked this solution. It
always nil at the end of method.
Here is my method:
- (BOOL)whois:(NSString *)domain withZone: (NSString*) zone
{
__block NSString *resultCode;
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
return YES;
}
return NO;
}
Instead of creating aNSOperationQueue, start your AFJSONRequestOperation with [operation start] and then call [operation waitUntilFinished] and it will block the main thread until it's finished. Then your resultCode should not be nil.
As #mattt said in the post you linked, it is strongly discouraged to freeze the thread like this. Consider figuring out another way to do this, such as calling a new method you hope to continue from your success block, and a different failure method from your failure block.
Your method can't function with its current design.
- (BOOL)whois:(NSString *)domain withZone: (NSString*) zone
{
__block NSString *resultCode;
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
// *** Runs 1st
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// *** runs 3rd
resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
// *** Runs 2nd
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
return YES;
}
return NO;
}
Because the material in the block runs third, and asynchronously, you won't be able to return that value to the greater method in the manner it is currently designed. Perhaps use something like this:
- (void)whois:(NSString *)domain withZone: (NSString*) zone
{
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
__weak id weakSelf = self;
// Runs 1st
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSString *resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
[weakSelf receivedResultCode:resultCode];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
}
- (void) receivedResultCode:(NSString *)resultCode {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
// do #YES stuff
}
else {
// do #NO stuff
}
}
Obviously you'll have to change the design of whoever's calling it because it won't return a value in the way you specified. Perhaps there is a better solution, but I think this is the type of design required for it to work.

AFNetworking - How to make POST request

EDIT 07/14
As Bill Burgess mentionned in a comment of his answer, this question is related to the version 1.3 of AFNetworking. It may be outdated for the newcomers here.
I'm quite new to iPhone development, and I'm using AFNetworking as my services library.
The API i'm querying is a RESTful one, and I need to make POST requests. To do this, I tried with the following code :
NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys:#"my_username", #"username", #"my_password", #"password", nil];
NSURL *url = [NSURL URLWithString:#"http://localhost:8080/login"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Pass Response = %#", JSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Failed Response : %#", JSON);
}];
[operation start];
There are two main issues with this code :
AFJSONRequestOperation seems to make a GET request, not a POST one.
I can't put parameters to this method.
I also tried with this code :
NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys:#"my_username", #"username", #"my_password", #"password", nil];
NSURL *url = [NSURL URLWithString:#"http://localhost:8080"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[httpClient postPath:#"/login" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Succes : %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure : %#", error);
}];
Is there a better way to make what I want here to get it done ?
Thanks for the help !
You can override the default behavior of your request being used with AFNetworking to process as a POST.
NSURLRequest *request = [client requestWithMethod:#"POST" path:path parameters:nil];
This assumes you have overridden the default AFNetworking setup to use a custom client. If you aren't, I would suggest doing it. Just create a custom class to handle your network client for you.
MyAPIClient.h
#import <Foundation/Foundation.h>
#import "AFHTTPClient.h"
#interface MyAPIClient : AFHTTPClient
+(MyAPIClient *)sharedClient;
#end
MyAPIClient.m
#implementation MyAPIClient
+(MyAPIClient *)sharedClient {
static MyAPIClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:webAddress]];
});
return _sharedClient;
}
-(id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
self.parameterEncoding = AFJSONParameterEncoding;
return self;
}
Then you should be able to fire off your network calls on the operation queue with no problem.
MyAPIClient *client = [MyAPIClient sharedClient];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
NSString *path = [NSString stringWithFormat:#"myapipath/?value=%#", value];
NSURLRequest *request = [client requestWithMethod:#"POST" path:path parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// code for successful return goes here
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
// do something with return data
}failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// code for failed request goes here
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
// do something on failure
}];
[operation start];

No Response from AFJSONRequestOperation

I'm trying to get the AFJSONRequestOperation to work with http://www.crowdkind.org/mobile/offers.
I get through the synchronous part and it returns data and puts it in the table just fine. When I go to convert it to AFJSONRequestOperation - I don't get a response. The operation is sent as the NSLog tells me that. But I never get "Operation succeeded or failed". Any idea what might be happening? Here's my searchBarButtonClicked method...
URLtoSearch is set to... http://www.crowdkind.org/mobile/offers. (feel free to click to as it's valid JSON).
- (void)searchBarSearchButtonClicked
{
searchResults = [NSMutableArray arrayWithCapacity:10];
[queue cancelAllOperations];
isLoading = YES;
[self.tableView reloadData];
NSURL *url = [self urlWithSearchText:URLtoSearch];
NSLog(#"URL: %#", URLtoSearch);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Operation succeeded");
[self parseDictionary:JSON];
[searchResults sortUsingSelector:#selector(compareName:)];
isLoading = NO;
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Operation failed");
[self showNetworkError];
isLoading = NO;
[self.tableView reloadData];
}];
operation.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", nil];
[queue addOperation:operation];
NSLog(#"Operation added");
}
I get no response. Check the link, it's valid json. Any thoughts greatly appreciated!