objective-c post data & image - objective-c

I am new to objective-c and I have been searching for a way to send a post request to my server (based on Rest URL) but also include an image with it... I have found many methods to post data... and methods to post just an image, but nothing that combines the two...
I am searching for a wrapper, class or library because it seems to be a tedious task to write all this from scratch. I found the "ASIHTTPRequest" but this is no longer supported, although Ic an turn off ARC, I would prefer to find something still supported...
I also found AFNetworking, which seems to still be supported but I could be wrong, I just cannot find a solution to combine VERY simply data and a profile image...
Any help is appreciated?
Should I just use the ASIHTTPRequest library... ?? Or does anyone have any sample code for the AFNetworking library?
Here is the code I am using for AFnetworking library...
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
_emailAddressField.text, #"email",
_usernameField.text, #"username",
_passwordField.text, #"password",
nil];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:%"http://url.com/api/whatever/"];
[client postPath:#"/" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSString *text = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"Response: %#", text);
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#", [error localizedDescription]);
}];

If you're using AFNetworking, you can use multipartFormRequestWithMethod to upload an image:
// Create the http client
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseUrl:url];
// Set parameters
NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys: #"param1", #"key1", nil];
// Create the request with the image data and file name, mime type, etc.
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:method path:#"url/to/" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:data name:nameData fileName:fileName mimeType:mimeType];
}];
And then you can add the upload progress block to get feedback of the upload process:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
//Manage upload percentage
}];
Also, you can add setCompletionBlockWithSuccess to catch success and failure in your operation. More info can be found here. At last but not least important, add the request to the operation queue:
[httpClient enqueueHTTPRequestOperation:operation];

Related

Having problems using AFNetworking fetching JSON

So i recently wanted to change from using NSUrlConnection to AFNetworking. I can receive the JSON data with both methods buts when using with AFNetworking something weird happens.
This is how it looks like with NSURLConnection
and this is how it looks like with AFNetworking
I have no idea what that (struct __lidb_autoregen_nspair) is and i dont know if that is the thing that is preventing me from displaying the data
This is the code from AFNetworking, i use the sample code from ray
-(void) fetchData{
// 1
NSURL *url = [NSURL URLWithString:string];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 2
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// 3
jsonDict = (NSMutableDictionary *)responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// 4
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Weather"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
// 5
[operation start];
}
----------------------------------------------------------------------------------------- Edit
-(NSMutableDictionary *) getAllGames{
[self fetchData];
DataParser *dataParserObjec = [[DataParser alloc] init];
return [dataParserObjec sendBackAllGames:jsonDict];
}
You are setting the acceptableContentTypes to text/html. I presume you are doing that because your web-service is not setting the correct Content-Type header to indicate that it's application/json. If you fixed the web service to provide the correct header Content-Type, you could then remove this acceptableContentTypes line in your Objective-C code.
If you're wondering why you didn't have to worry about that with NSURLConnection, that's because NSURLConnection doesn't do any validation of Content-Type (unless, of course, you write your own code in, for example, didReceiveResponse, that checked this).
You suggest that you are unable to display the data. But yet there it is, in your second screen snapshot. I personally would be less worried about internal representation than whether I could access the data from the NSDictionary. If you
NSLog(#"responseObject=%#", responseObject);
at #3, inside the success block, what precisely do you see? I'd wager you'll see your NSDictionary fine (despite the subtle differences in the internal representation).
My contention is that you are getting the data back successfully. Yes, your web service should set the correct Content-Type header so you don't have to overwrite the acceptableContentTypes value, but it looks like AFNetworking is retrieving your data fine.
The likely issue is that your main thread is trying to use jsonDict before the asynchronous network request is done. So the trick is to defer the use of the jsonDict until the asynchronous request is done.
You've updated your question showing us that you're instantiating a DataParser and calling sendBackAllGames. You should put that code inside the completion block of your asynchronous network request.
Alternatively, you could use a completion block pattern in your fetchData method, and then the getAllGames method could supply the sendBackAllGames code in a completion block that fetchData calls inside the success block of the AFHTTPRequestOperation.
If you used the completion block pattern, it would look like
-(void) fetchDataWithCompletionHandler:(void (^)(id responseObject, NSError *error))completionHandler {
NSURL *url = [NSURL URLWithString:string];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
// removed once we fixed the `Content-Type` header on server
//
// operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[[UIAlertView alloc] initWithTitle:#"Error Retrieving Weather"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
if (completionHandler) {
completionHandler(nil, error);
}
}];
[operation start];
}
And you'd call it like so:
[self fetchDataWithCompletionHandler:^(id responseObject, NSError *error) {
if (responseObject) {
DataParser *dataParserObjec = [[DataParser alloc] init];
NSMutableDictionary *results = [dataParserObjec sendBackAllGames:jsonDict];
// do whatever you want with results
}
}];
If the method that called getAllGames needed the data to be returned, you'd repeat this completion block pattern for this method, too.

Http Header Authentication on post request AFNetworking

I am trying to create a multipartFormRequestWithMethod using AFNetworking's AFHttpClient. It must authenticate with a ASP.NET REST service using Http Authorization Header. Therefore I pass username and password to the Http Authorization Header using setAuthorizationHeaderWithUsername:password:. In the body of the request I am passing a large file, several MBs. If the Header authentication fails, I want the request to get block and go in failure state before finishing the file send. There should be something that prevents the database send in case of authentication failure, but I cannot figure out what. In the current situation the AFHttpRequestOperation starts to send the file and notifies of the error only at the end of the file send.
This is the code:
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://v-moxdevelop/MOX.UploadDBService/UploadDB/"]];
NSString *psw = #"psw";
NSString *userName = #"username";
[httpClient setAuthorizationHeaderWithUsername:selectedLoginItem.user password:#"psw"];
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:#"POST" path: [NSString stringWithFormat:#"Upload/%#/%#/%#", UDID, [selectedLoginItem.firm stringByReplacingOccurrencesOfString:#"." withString:#""], [selectedLoginItem.user stringByReplacingOccurrencesOfString:#"." withString:#""]] parameters:nil constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFileData:fileData name:zipFilePath fileName:[zipFilePath lastPathComponent] mimeType:#"application/zip"];
}];
[request setTimeoutInterval:INT32_MAX];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
__weak typeof(self) weakSelf = self;
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *locOperation, id responseObject) {
}failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
}];
[httpClient enqueueHTTPRequestOperation:operation];

AFNetworking getting data for XML parse error

This is my AFHTTPClient singleton:
+ (API *)sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[API alloc] initWithBaseURL:[NSURL URLWithString:kAPIHost]];
[sharedInstance setParameterEncoding:AFJSONParameterEncoding];
[sharedInstance registerHTTPOperationClass:[AFXMLRequestOperation class]];
[sharedInstance setDefaultHeader:#"Accept" value:#"application/rss+xml"];
});
return sharedInstance;
}
And method in same class (AFHTTPClient):
- (void)requestXMLDataCompletion:(JSONResponseBlock)completionBlock
{
NSMutableURLRequest *apiRequest = [self requestWithMethod:#"GET" path:kAPIPath parameters:nil];
AFXMLRequestOperation *operation = [[AFXMLRequestOperation alloc] initWithRequest:apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
// success
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
When I call this function to get XML from RSS I get this error:
error = "Expected content type {(\n \"application/xml\",\n \"text/xml\"\n)}, got application/rss+xml";
Question:
Is whole concept of implemented singleton good and do I need any changes ?
Is there any suggestion if whole concept is wrong ?
Why am I getting this error?
Thanks.
Concept of Singleton
A singleton is more commonly known as a design pattern.
Usually a singleton is a class and behaves exactly like any other class,
the only exception being that any instances of a singleton reference the
same object data. This means that any instance of a singleton class are
actually all the same instance.
You can check out Singleton Pattern for more information and sample code to enforce how the singleton will be used.
Is there any suggestion if whole concept is wrong ?
I would suggest you to use Singleton for AFNetworking since you will have
only one instance of it.
Your Error
The error you are getting is because AFNetworking request wants Header Content-Type as "application/xml" or "text/xml"
Try changing this code:
[self registerHTTPOperationClass:[AFXMLRequestOperation class]];
to
[self registerHTTPOperationClass:[AFHTTPRequestOperation class]];
I had a similar problem:
Error Domain=AFNetworkingErrorDomain Code=-1016 "Expected content type {(
"text/xml",
"application/xml"
)}, got application/rss+xml"
The answer above is not full and clear, although it helped me a lot after I read their chat. registerHTTPOperationClass doesn't help. I decided to provide some code. Solution is to NOT use this:
[AFXMLRequestOperation XMLParserRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser)
But download RSS XML using AFHTTPRequestOperation and create NSXMLParser manually:
NSString *articlesUrlString = #"http://pro.rabota.ru/feed/moscow.content.rss";
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:articlesUrlString]];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSData *xmlData = (NSData *)responseObject;
NSXMLParser *XMLParser = [[NSXMLParser alloc] initWithData:xmlData];
XMLParser.delegate = self;
[XMLParser parse];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}];

JSON Response in postPath AFHTTPClient

I just switched to RestKit 0.2 and I am currently using the new "HttpClient" which is basically a AFHTTPClient. I have this line of code:
RKObjectManager* objectManager = [RKObjectManager sharedManager];
NSDictionary* params = [[NSDictionary alloc] initWithObjectsAndKeys: login, #"username", password, #"password", nil];
[[objectManager HTTPClient]postPath:#"users/login/?format=json" parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
//reponseObject vs operation.response
NSLog(#"%#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"ERROR");
}];
This POST calls return a JSON response in the form: {"api_key":"....","username":"...."}. As simple as that.
Before switching to 0.2, I was able to get the api_key key in the response by doing:
[[RKClient sharedClient] post:#"/users/login/?format=json" usingBlock:^(RKRequest *request)
{
request.onDidLoadResponse = ^(RKResponse *response)
{
id parsedResponse = [response parsedBody:NULL];
NSString *apiKey = [parsedResponse valueForKey:#"api_key"];
}
}.....];
http://restkit.org/api/master/Classes/RKResponse.html
But now, I can't do that and if I do a NSLog on the responseObject, I get:
<7b227265 61736f6e 223a2022 41504920 4b657920 666f756e 64222c20 22617069 5f6b6579 223a2022 61356661 65323437 66336264 35316164 39396338 63393734 36386438 34636162 36306537 65386331 222c2022 73756363 65737322 3a207472 75657d>
And the weird thing is that if I do:
NSLog(#"%#", operation.responseString);
I do have the JSON (in NSString) showing up.
So two questions:
1) Why is printing the responseObject showing me HEX code, and not the actually JSON response?
2) Why if I do operation.responseString it is showing the actual Response Object? Is there a way to get the actual data in ResponseObject after being parsed from the JSON?
AFNetworking should instantiate a AFJSONRequestOperation. Probably it creates a basic AFHTTPRequestOperation instead (check [operation class]) resulting in a NSData object as response.
Make sure you register the operation class in the init method of your AFHTTPClient subclass (initWithBaseURL):
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
You could also try to use AFJSONRequestOperation directly like this:
NSURLRequest *request = [[objectManager HTTPClient] requestWithMethod:#"POST" path:#"users/login/?format=json" parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"JSON: %#", JSON);
} failure:nil];
[[objectManager HTTPClient] enqueueHTTPRequestOperation:operation];
What you are seeing, if I'm not mistaken, is the raw bytes from the NSData that is given to you when your success block is called.
The hex you posted reads:
{"reason": "API Key found", "api_key": "a5fae247f3bd51ad99c8c97468d84cab60e7e8c1", "success": true}
The reason the second NSLog shows you what you want is that the %# format string calls the description (correct me if I'm wrong here, SO) of the object you pass it and the NSData probably knows it is a string underneath.
So, on to how to get the JSON. It is really rather simple. Once you have your response object, you can do something like this:
NSDictionary* jsonFromData = (NSDictionary*)[NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
What this will do for you is use return an NSDictionary which encodes the root object in the JSON and then each value in the dictionary will be of the type NSString, NSNumber, NSArray, NSDictionary, or NSNull. See NSJSONSserialization for documentation.
The NSJSONReadingMutableContainers makes the dictionaries and arrays mutable. It's just a leftover from my code.
Hopefully you're on iOS 5 or later, or you'll need to find another solution for the parsing.
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSData *responseData = operation.HTTPRequestOperation.responseData;
id parsedResponse = [RKMIMETypeSerialization objectFromData:responseData MIMEType:RKMIMETypeJSON error:nil];
NSString *apiKey = [parsedResponse valueForKey:#"api_key"]
}

AFNetworking EXC_BAD_ACCESS in setCompletionBlock

I'm working on an iOS app and am having some trouble with making a http request using AFNetworking.
When I run the code I get the error: EXC_BAD_ACCESS(code=2 address=0x0).
The error is occurring when I attempt to setCompletionBlock.
I'm new to Objective-C and this has me stumped.
Thank you in advance. Everyone's help is appreciated!
#import "AFNetworking.h"
#import <Cordova/CDV.h>
#import "UploadImg.h"
#implementation UploadImg
- (void) uploadImg:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options{
NSURL *url = [NSURL URLWithString:#"http://test.com/mobile/"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSData *imageData = [NSData dataFromBase64String:[arguments objectAtIndex:1]];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"TEST_STYLE" forKey:#"styleType"];
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:#"POST" path:#"/upload.php" parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFileData:imageData name:#"imageName" fileName:#"image.png" mimeType:#"image/png"];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error");
}];
[operation start];
}
#end
Thanks again!
I was able to solve this by changing the build settings.
Under the linking header change the "Other linker flags", click the entry for -weak_library and replace it with -weak-lSystem
From question: Use of Blocks crashes app in iPhone Simulator 4.3/XCode 4.2 and 4.0.2
Welcome to the fun world of Objective-C, #kev_addict!
When you get an EXC_BAD_ACCESS exception, it helps to have the details, to see what the stack trace looks like for the offending call.
Without any additional information, my gut tells me that your problems lie somewhere in the way you're getting the image data from an array. Is there any reason why you wouldn't have this method take a UIImage argument? It seems odd to expect that this method takes an array that expects image data--in the second position, no less.