Possible to add property to object in array in blockopertion (AFNetwork)? - ios

I'm making several request from different sources, and because of this I want to add a property like: '"newsSource" = twitter' (JSON format) to the created NSArray resultsTwitter below. The reason is I want be able to handle each "newsitem" uniquely.
I'm new to blocks, but I think it might be an really easy way to do this "on the fly"?
If not possible within the block operation, any suggestion on how to do it after operation is done?
// Fetch data from Twitter (json complient)
NSURLRequest *request = [NSURLRequest requestWithURL:urlTwitter];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *req, NSHTTPURLResponse *responce, id jsonObject) {
NSLog(#"Responce: %#",jsonObject);
self.resultsTwitter = [jsonObject objectForKey:#"results"];
[self.tableView reloadData];
}
failure:^(NSURLRequest *req, NSHTTPURLResponse *responce, NSError *error, id jsonObject) {
NSLog(#"Recieved an HTTP %d", responce.statusCode);
NSLog(#"The error was: %#",error);
}];
[operation start];

I may not have understood your question correctly, but as long as resultsTwitter is a NSMutableArray, you can add an object (in your case an NSDictionary with a single KVP) after it is initially populated.
Something like:
[resultsTwitter addObject:[NSDictionary dictionaryWithObjectsAndKeys:
#"twitter", #"newsSource",
nil]];
Example of instantiating a variable that can be accessed inside a block:
__block NSString *newssource = #"";
NSURLRequest *request = [NSURLRequest requestWithURL:urlTwitter];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *req, NSHTTPURLResponse *responce, id jsonObject) {
NSLog(#"Responce: %#",jsonObject);
self.resultsTwitter = [jsonObject objectForKey:#"results"];
[self.tableView reloadData];
newssource = #"twitter";
}
failure:^(NSURLRequest *req, NSHTTPURLResponse *responce, NSError *error, id jsonObject) {
NSLog(#"Recieved an HTTP %d", responce.statusCode);
NSLog(#"The error was: %#",error);
}];
[operation start];

Create a Model class to encapsulate the behavior of all News Items.
This pattern is used in the AFNetworking example app, with each App.net post corresponding to a model object, which is initialized from JSON. I would strongly recommend against using a mutable dictionary rather than a model object as a means of representing items.

Related

Parsing JSON Help IOS

I currentley have a jSON file which is created a by a script which can be seen here: http://ddelay.co.uk/bus/output.json
I currently have managed to setup the following to grab the JSON which works, i just cannot figure how to grab the data to assign it :/
-(void)makeStopRequests{
NSURL *url = [NSURL URLWithString:#"http://ddelay.co.uk/bus/output.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//AFNetworking Asynchronous Task
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
{
NSLog(#"JSON RESULT %#", responseObject);
self.stopArray = [responseObject objectForKey:#"stop_name"];
[self.tableView reloadData];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id responseObject)
{
NSLog(#"Request Failed: %#, %#", error, error.userInfo);
}];
[operation start];
}
I hope somebody can guide me through how I would select the data,
Thank you.
Damien
I would recommend just using something as simple as NSJSONSerialization, which is built into iOS after iOS 5 (may have been earlier, but not too sure).
Just get the data from the request and then parse that into a dictionary with the following code
NSDictionary *dict= [NSJSONSerialization JSONObjectWithData:webData options:NSJSONReadingMutableLeaves error:nil];
get the data using the standard built in networking structure as well
NSURLRequest *someRequest = [[NSURLRequest alloc] initWithURL:someURL cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:10.0];
NSURLConnection *someConnection= [[NSURLConnection alloc] initWithRequest:someRequest delegate:self];
And then do whatever appropriate delegation and data processing. Read the documentation to get more details
You are using AFNetworking and it provides you the response object. That is already being cast to Objective c object.
You can simply assign it to NSArray or NSDictionary like this
NSArray *array = (NSArray*)responseObject;//if your json returns an array
NSDictionay *dict (NSDictionary*)responseObject;//if json returns dictionary.
This url returns JSON in this format;
[ {
"service":"22",
"provider":"First in Yorkshire",
"dest":"Nether Edge to Woodhouse",
"dest URL":"/web/public_service_stops.asp?service=22&operatorid=31&systemid=30&goingto=Woodhouse"
},
{
"service":"22",
"provider":"First in Yorkshire",
"dest":"Barnsley to Rotherham",
"dest URL":"/web/public_service_stops.asp?service=22&operatorid=31&systemid=30&goingto=Rotherham"
}
]
Which actually is an array of dictionaries so to access data you have to loop like this.
NSArray *jsonResponse = (NSArray*)responseObject;
for (NSDictionary *dic in jsonResponse){
NSString *service = [dic valueForKey:#"service"];
NSString *provider = [dic valueForKey:#"provider"];
//Same for others
//It will better you create calss with these properties and then add that object to an Array, and on reloading data in table get that object from array in cellForRowAtIndexPath and use requried property to populate your data.
//Or if you just want to use stop name add stop name to array and use it.
NSString *stopName =[dic valueForKey:#"stop_name"];
//[dataArray addObject:stopName]; in case you want to use only stop name
}
Here's minimal code:
self.stopArray = responseObject;
for (NSDictionary *stopDict in self.stopArray) {
NSString *service = [stopDict objectForKey:#"service"];
NSString *provider = [stopDict objectForKey:#"provider"];
NSString *dest = [stopDict objectForKey:#"dest"];
NSString *destURL = [stopDict objectForKey:#"dest URL"];
// do something with this data
}
For anything more complex than this, you'll probably want to make your own data class, so you don't have to parse through dictionaries and arrays in your table view delegate methods.

AFNetworking : Handling of asynchronous get request with AFJSONRequestOperation

I have the following code inside a class (static method) which I call to get data from an API. I decided to make this a static method just so I can reuse it on some other parts of the app.
+ (NSArray*) getAllRoomsWithEventId:(NSNumber *)eventId{
NSURL *urlRequest = [NSURL URLWithString:[NSString stringWithFormat:#"http://blablba.com/api/Rooms/GetAll/e/%#/r?%#", eventId, [ServiceRequest getAuth]]];
NSMutableArray *rooms = [[NSMutableArray alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:urlRequest];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Response of getall rooms %#", JSON);
NSArray *jsonResults = (NSArray*)JSON;
for(id item in jsonResults){
Room* room = [[Room alloc]init];
if([item isKindOfClass:[NSDictionary class]]){
room.Id = [item objectForKey:#"Id"];
room.eventId = [item objectForKey:#"EventId"];
room.UINumber = [item objectForKey:#"RoomUIID"];
[rooms addObject:room];
}
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
NSLog(#"Error");
}];
[operation start];
[operation waitUntilFinished];
return rooms;
}
Now my issue is, whenever I call this in a ViewController (ViewDidLoad method). The static method will run till the end and will return null on the rooms, but the Nslog will display the "Success" block Nslog a few seconds after. Now I understand that this is asynchronous so it doesn't wait for the success block to execute before it reaches the "return rooms;" line. With all that said, I need some advice as to how to handle this, like maybe a progress bar or something like that? Or something that delays it? I'm not really sure if that's the reight way or if it is, I am not sure how to do it.
Any advice is very much appreciated. Thank you!
AFNetworking is built around asynchronicity—starting a request, and then executing some piece of code once that request has finished.
waitUntilFinished is an anti-pattern, which can block the user interface.
Instead, your method should have no return type (void), and have a completion block parameter that returns the serialized array of rooms:
- (void)allRoomsWithEventId:(NSNumber *)eventId
block:(void (^)(NSArray *rooms))block
{
// ...
}
See the example app in the AFNetworking project for an example of how to do this.
You can write your method following way:
+ (void) getAllRoomsWithEventId:(NSNumber *)eventId:(void(^)(NSArray *roomArray)) block
{
NSURL *urlRequest = [NSURL URLWithString:[NSString stringWithFormat:#"http://blablba.com/api/Rooms/GetAll/e/%#/r?%#", eventId, [ServiceRequest getAuth]]];
NSMutableArray *rooms = [[NSMutableArray alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:urlRequest];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Response of getall rooms %#", JSON);
NSArray *jsonResults = (NSArray*)JSON;
for(id item in jsonResults){
Room* room = [[Room alloc]init];
if([item isKindOfClass:[NSDictionary class]]){
room.Id = [item objectForKey:#"Id"];
room.eventId = [item objectForKey:#"EventId"];
room.UINumber = [item objectForKey:#"RoomUIID"];
[rooms addObject:room];
}
}
block(rooms);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
NSLog(#"Error");
block(nil); //or any other error message..
}];
[operation start];
[operation waitUntilFinished];
}
you can call this method like followings:
[MyDataClass getAllRoomsWithEventId:#"eventid1":^(NSArray *roomArray) {
NSLog(#"roomArr == %#",roomArray);
}];

How to wrap AFNetworking AFJSONRequestOperation into a separate datamanager class

I'm new to using blocks in iOS and I am thinking that's probably the crux of my problem.
I just want to build a simple static DataManager class whose sole job is to fetch data from my Restful service.
I would call this from all my various UIViewControllers (or collectionview/table controllers)
In my class i have a function that looks like this
+ (NSArray *) SearchByKeyword: (NSString*) keyword {
__block NSArray* searchResults = [[NSArray alloc] init];
NSString *baseURL = #"http://someURL.com/api/search";
NSString *requestURL = [baseURL stringByAppendingString:keyword];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET"
path:requestURL
parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
searchResults = [JSON valueForKeyPath:#""];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
return searchResults;
}
However, this keeps returning zero data. Can someone suggest the right way of doing this?
You are trying to use the results of an asynchronous task (the JSON operation) as the return value for a synchronous method call, so that is why you get no data.
You could provide your view controllers with an API that takes completion blocks and failure blocks, similar to the AF networking one. View controllers can then do what they need to do with the results when they are passed into the block.
Modifying your code from your question:
typedef void (^SearchCompletionBlock)(NSArray *results);
typedef void (^SearchFailureBlock)(NSError *error);
+ (void)searchByKeyword:(NSString*)keyword completionBlock:(SearchCompletionBlock)completionBlock failureBlock:(SearchFailureBlock)failureBlock;
{
NSString *baseURL = #"http://someURL.com/api/search";
NSString *requestURL = [baseURL stringByAppendingString:keyword];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET"
path:requestURL
parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
if (completionBlock) {
completionBlockc([JSON valueForKeyPath:#""]);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
if (failureBlock) {
failureBlock(error);
}
}];
[operation start];
}
Then clients could pass completion blocks that stored the results and reloaded their views. Something like:
^ (NSArray *results) {
self.results = results;
[self.tableView reloadData];
}
Your JSON request operation is asynchronous, meaning that it will kick off the request ([operations start], then immediately return your results, which will be empty. When the completion block runs, it assigns your data but nothing is done with it. Your search method can't return an object unless it waits for the request to complete.
You've got a few options:
Pass in a completion block to the search method which does something with the results. The completion block is called in the completion block of the request, once all the service-specific stuff (processing JSON etc) is finished. (Block inception!)
Have the completion block of the request assign a property of the data manager, then call a delegate method or notification to let others know the results are available.
I'd prefer option 1.

AFNetworking http client not sending JSON parameters

I created a subclass of AFHTTPClient and am trying to send some JSON parameters to a server.
However the server is responding with a Expected content type
{(
"text/json",
"application/json",
"text/javascript"
)}, got application/xml
According to AFNetworking FAQ
If you're using AFHTTPClient, set the parameterEncoding property to AFJSONParameterEncoding. Any method on that HTTP client with a parameters argument will now encode the passed object into a JSON string and set the HTTP body and Content-Type header appropriately.
I've done that here but the server appears not to recognize the content-headers. Does anyone know of a potential solution?
Here is the method:
- (void)getCompanyDataWithString:(NSString*)companySearchQuery
finish:(LBMarkitAPIRequestCompletionBlock)finishBlock
{
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setParameterEncoding:AFJSONParameterEncoding];
NSDictionary *params = [NSDictionary dictionaryWithObject:
companySearchQuery forKey:#"input"];
NSMutableURLRequest *searchQueryRequest = [self requestWithMethod:#"GET"
path:kMarkitCompanyURL parameters:params];
AFJSONRequestOperation *searchRequestOperation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:searchQueryRequest
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id json)
{
NSLog(#"Response: %#", response);
NSLog(#"JSON: %#",json);
NSMutableArray *results = [NSMutableArray array];
NSError *anError = [[NSError alloc] init];
if ([json objectForKey:#"Message"])
{
NSString *message = [json objectForKey:#"Message"];
anError = [[NSError alloc] initWithDomain:message
code:100
userInfo:nil];
}
// Need some error handling code here
for (id item in json)
{
NSString *aName = [item objectForKey:#"Name"];
NSString *aSymbol = [item objectForKey:#"Symbol"];
NSString *anExchange = [item objectForKey:#"Exchange"];
LBCompany *aCompany = [[LBCompany alloc] initWithName:aName
Symbol:aSymbol Exchange:anExchange];
[results addObject:aCompany];
}
// Need to run the passed in block after JSON
// Request Operation succeeds
finishBlock(results,anError);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response,
NSError *error, id JSON)
{
NSLog(#"request failed: %#",[error localizedDescription]);
NSLog(#"Response: %#",response);
NSLog(#"JSON: %#",JSON);
}];
[searchRequestOperation start];
NSLog(#"JSON operation started");
}
The issue was with URL formatting. I didn't notice an API implementation detail that made sending Query Parameters necessary and also specifying JSON output in the URI.
There were no issues with regard to AFNetworking.

AFNetworking+UIImageView placeholder image shows up, but not URL image

I'm trying to use the AFNetworking UIImageView call to load images from a URL as shown below:
[self.image setImageWithURL:[NSURL URLWithString:feed.imageURL] placeholderImage: [UIImage imageNamed:#"logo"]];
The placeholder image always shows up, but the actual image from "feed.imageURL" never does. I've verified that the URL is actually correct. I even hardcoded it to make sure, and still nothing.
My basic app setup is a tab controller...and in viewDidLoad, I call a method "fetchFeed" which performs the HTTP request to gather my JSON data.
My request block looks like:
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[self parseDictionary:JSON];
isLoading = NO;
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Error: %#", error);
[self showNetworkError];
isLoading = NO;
[self.tableView reloadData];
}];
operation.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", #"text/html", nil];
[queue addOperation:operation];
Turns out the server I was requesting the image from was sending content-type "image/jpg" and by default AFNetworking does not support this file type.
I changed the class method in AFImageRequestOperation to look like:
+ (NSSet *)defaultAcceptableContentTypes {
return [NSSet setWithObjects:#"image/tiff", #"image/jpeg", #"image/gif", #"image/png", #"image/ico", #"image/x-icon" #"image/bmp", #"image/x-bmp", #"image/x-xbitmap", #"image/x-win-bitmap", #"image/jpg", nil];
}
and it fixed my problem.
You can manage to accept what content-type you want with this library simply changing the request like this:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:yourURL];
[request addValue:#"image/*" forHTTPHeaderField:#"Accept"];
And call the AFNetworking method:
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
This way you will be able to override the content-type without changing the library.
AFNetworking doesn't support image/jpg MIME TYPE by default.
You can support it without modifying the AFNetworking Library
[AFImageRequestOperation addAcceptableContentType:#"image/jpg"];
All operations that manipulate the UI must be performed on the main thread. So you may need to use 'performSelectorOnMainThread:' when reloading your tableview data in the completion block.
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO]
I had a similar problem but it turned out that I was passing a URL which contained spaces in it. When I properly encoded the URL using stringByAddingPercentEscapesUsingEncoding: the images now load.

Resources