This seems like such a simple thing to do but I just can't put it together. I have an iOS app using RestKit 0.20 to populate my CoreData attributes. My main view controller is a collection view which is populated at startup by making a request to the server.
When a user selects a cell I am able to transfer the data contained in that cell to the detail view (an image and a title). But I also need to make another request to the server to obtain all of the information to be shown in the detail viewController. The Entity name is Gist and the Attribute is fullArticle
Here is the segue to the detailViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"newsDetail"]) {
NSIndexPath *indexPath = [[self.collectionView indexPathsForSelectedItems] lastObject];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[[segue destinationViewController] setmanagedObjectContext:managedObjectContext];
[[segue destinationViewController] setDetailItem:object];
}
}
Here is what i use in -(void)viewDidLoad
{
[super viewDidLoad];
NSString *articleID =[self.detailItem valueForKey:#"articleId"];
NSString *personID = [self.detailItem valueForKey:#"userID"];
NSString *getArticle =[NSString stringWithFormat:#"/rest/article/getArticleForView?aid=%#&pid=%#&ip=255.255.255.0",articleID,personID];
[[RKObjectManager sharedManager] getObjectsAtPath:getArticle parameters:nil success:nil failure:nil];
NSURL *photoURL =[NSURL URLWithString:[self.detailItem valueForKey:#"imageUrl"]];
NSData *photoData = [NSData dataWithContentsOfURL:photoURL];
self.newsDetailImageView.image =[UIImage imageWithData:photoData];
//This is where I am stuck, How do you fetch the Attribute "fullArticle" from the request that I just made?
self.newsDetailText.text = //valueForKey:"fullArticle" from article with ID `articleID`
}
I have tried using the code below but the fetchedResultsController is setup for use with a tableView so there is no way I know of to specify without an indexPath
NSManagedObject *detailText = [self.fetchedResultsController objectAtIndex:0];
self.newsDetailText.text = [[detailText valueForKey:#"fullArticle"] description];
When tried this way fullArticle is null presumably because objectAtIndex:0 is not the right way to designate what object I need to fetch.
Please shed some light on this for me! Clearly a rookie question so code snippets really help! Thanks in advance!
Solution with help from Wain
Wain pointed out that the request does not take place immediately. I needed to use the callbacks in the request to acquire the data I was wanting. Restkit provides the mappingResults in a variety of ways. You can read about that here under RKMappingResult.
Here is how I made it work.
//I REPLACE MY SERVER REQUEST WITH THIS
[[RKObjectManager sharedManager] getObjectsAtPath:getArticle parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//HERE IS HOW I SET THE TEXT FIELD IN THE DETAIL VIEW CONTROLLER
self.newsDetailText.text = [[mappingResult.firstObject valueForKey:#"fullArticle"]description];
// ERROR HANDLING
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
The request you make doesn't get the results from the server immediately. You should implement the callbacks to handle success and error responses and use the supplied mapping result to get the info to display.
Related
I am working on iOS App, and I am using AFNetworking for interacting with server API.
My issue is I want to send call and don't want to restrict user until response get from server, so issue is crash. When user move back to that particular screen lets say I have listing screen where I am getting data which is taking 6-7 seconds and meanwhile user move back to previous screen and when data come from API and call back that delete to listing screen but user move backed to that screen then App crashes
Here below is code for fetching data call.
+ (void) getRequestForDocumentListing:(NSDictionary *)headerParams urlQuery: (NSString*)action parameters:(NSDictionary*)params
onComplete:(void (^)(id json, id code))successBlock
onError:(void (^)(id error, id code))errorBlock
{
NSString *authorizationValue = [self setAuthorizationValue:action];
NSString *selectedLanguage = [ApplicationBaseViewController getDataFromDefaults:#"GLOBALLOCALE"];
NSString *language = selectedLanguage;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//set headers values
[manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[manager.requestSerializer setValue:language forHTTPHeaderField:#"Accept-Language"];
[manager.requestSerializer setValue:authorizationValue forHTTPHeaderField:#"authorization"];
[manager.requestSerializer setValue:#"x-folder" forHTTPHeaderField:#"inbox"];
[manager GET:action parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"document listing success");
NSInteger statusCode = [operation.response statusCode];
NSNumber *statusObject = [NSNumber numberWithInteger:statusCode];
successBlock(responseObject, statusObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSInteger statusCode = [operation.response statusCode];
NSNumber *statusObject = [NSNumber numberWithInteger:statusCode];
id responseObject = operation.responseData;
id json = nil;
id errorMessage = nil;
if (responseObject) {
json = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&error];
errorMessage = [(NSDictionary*)json objectForKey:#"Message"];
}else{
json = [error.userInfo objectForKey:NSLocalizedDescriptionKey];
errorMessage = json;
}
errorBlock(errorMessage, statusObject);
}];
}
What I need is to stop call in ViewdidDisappear View delegate
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:#"GET" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
How to solve this particular issue?
I got your point, I think the problem is not about the AFNetWorking or download, it is about how you organize your view controllers.
In short, you need to make sure the synchronization of the data and view.
What cause your crash is when users do some operation(eg. delete, move...), the data is not the same with what view shows.
Let's play back an example:
An array with 12 objects and show it with a table view.
User call a web request to change the array. As we know, it needs time.
User leave and come back again. In this view, table view shows with the old array.
At this point, web request comes back. The array is modified to 10 object.But at this time, the call back dose not cause the table view to load the new data.
When user do some operation, just like delete the 11st object in the table view. Actually, there is no 11st object in array.
So crash comes.
How to deal with it is to keep the synchronization of the data and view.
First get a reference to the Operation object by
AFHTTPRequestOperation *operation = [manager GET:action parameters:nil success:^...blah blah blah...];
Then you can set the completion block to nil when you move away from this screen.
[operation setCompletionBlock:nil];
Please note that even though you move away from the screen, the request may actually execute successfully. However, your app will not crash now.
Thanks RuchiraRandana and childrenOurFuture for your answer, I got help from your answers and finally I come to solution where I am not going to cancel operation and set nil delegate, because my others operation are also in working which is trigger on other screen.
I create a just BOOL and set YES default value in singleton class and also set to no in - (void)dealloc on that particular class and in API class where I am triggering that delegate I added that check.
if ([SHAppSingleton sharedInstance].isDocListControllerPop == YES) {
[delegate documentListResponse:documentList andStatusCode:code];
}
I know this might not be perfect solution but this resolved my issue.
Thanks
Having this issue, because I'm trying to develop my code. Before, I was using AFNetworking methods in the classes, but I got 4 of them. Instead of that repeatin sequence, I wanted to have APIClient, which has the methods. I implemented some methods but my issue is about just two of them.
So, in APIClient.m I have the followings:
+(void)GetCurrencyInformationFrom:(NSString *)URLString to:(NSArray *) array inThe:(UITableView *) tableView{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // User informations.
NSString *accessToken = [defaults objectForKey:#"accessToken"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:accessToken forHTTPHeaderField:#"Token"];
NSLog(#"access token: %#", accessToken);
NSLog(#"id: %#", [defaults objectForKey:#"ID"]);
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self update:array withDictionary:responseObject inThe:tableView];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error 2: %#", error);
}];
}
+(void)update:(NSArray *)array withDictionary:(NSDictionary *)dictionary inThe:(UITableView *) tableView{
NSLog(#"Data Count: %lu", [dictionary[#"Data"] count]);
array = [[NSMutableArray alloc] initWithArray:dictionary[#"Data"]];
NSLog(#"Array Count: %lu", [array count]);
[tableView reloadData];
}
Those methods are called in Table View classes. For example, one of my classes I called within the viewload those:
NSString *URL = #"http://api-dvzalt.azurewebsites.net/api/Currency/Doviz";
[APIClient GetCurrencyInformationFrom:URL to:currencyArray inThe: tableView];
For debugging, I am printing the Data count and Array count (Both you can find in update:withDictionary:inThe: method) and number of rows (in the table class). It's normal to number of rows to be zero at the beginning since it is asychronous, however, after I reload my tableView, i.e. after everything is done (see [tableView reloatData] in update:withDictionary:inThe method) number of rows remains zero, where Data and Array's count are 20. And of course, with zero rows, nothing showed up on the table. So, basically my problem is the currencyArray I'm giving to method doesn't change after it comes back to the tableView again even it is changing in the APIClient class.
I feel like it is a simple mistake, but I can't see where it is. Glad if you can help me to find it.
Thanks!
Did you make sure that your UITableView has the correct data source set?
What do your -numberOfSectionsInTableView: and -tableView:numberOfRowsInSection: methods look like?
I don't really like answering my question. However in this case, I bet there are too many newbie people around and searching for the same problem I have. I couldn't find any solution for 4 days. Have been searching on net and asking here, but nowhere I found the solution I needed. Maybe it will be not the case for you, but certainly it will at least take 1 day long, if it is the first time. So, I will give a try to make beginners like me understand deeply.
At first, creating and having a class like APIClient is generally a good idea. It is just a class that you can easily use when you are going to take data from internet. However, things are getting complicated for beginners since we are mostly got used to synchronous execution.
If you are up trying modify your any instance in any class, what you have to do is, simply, not to give that instance to APIClient (or the class that has blocks) like me, instead trying to take any needed information from the APIClient. Like, if we can achieve the information coming from the APIClient, it is easy to that instance in the instance's own class. E.g. giving currencyArray to APIClient, and trying to update the array in APIClient is hard. On the other hand, it is easy to just taking the responceObject from APIClient, which will be exactly the JSON data coming from the URL you have.
Long story short, in that manner, I changed my code in the APIClient.m into this:
+(void)GetCurrencyInformationFrom:(NSString *)URLString to:(NSArray *) array inThe:(UITableView *) tableView{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // User informations.
NSString *accessToken = [defaults objectForKey:#"accessToken"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:accessToken forHTTPHeaderField:#"Token"];
NSLog(#"access token: %#", accessToken);
NSLog(#"id: %#", [defaults objectForKey:#"ID"]);
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if(success) success(operation, responseObject)
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if(failure) failure(operation, error)
}];
}
Note that I have no longer update:withDictionary:inThe: method, since the plan is taking the information to the instance's class, and update it there. not here.
For updating purpose, let's call in the viewLoad of the instance's class. So, the method will look like this:
[APIClient GetCurrencyInformationFrom:URL
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self updateCurrencyArrayWithDictionary:responseObject];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error 2: %#", error);
}];
Again for updating purpose, I also added update method here instead of APIClient, which is not really necessary actually; instead we would have block type instance.
-(void)updateCurrencyArrayWithDictionary:(NSDictionary *)dictionary{
currencyArray= [[NSMutableArray alloc] initWithArray:dictionary[#"Data"]];
[tableView reloadData];
}
Please note that the line dictionary[#"Data"]]; would probably change according to your data.
So, that's it. This is just a simple example of how to create API client for the networking purpose of our application.
Hope, I can help someone in the future with this post.
I have 2 table views in 2 view controllers. When i select a cell from first screen i want to load the second table , but my problem is : the table is empty and is populating after i scroll.
This is how i populate my table :
When cell is selected :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSURL *url = [NSURL URLWithString:#"http://privatereisen.com/"];
ProgViewController *cd = [self.storyboard instantiateViewControllerWithIdentifier:#"progidentifview"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSString *path=[NSString stringWithFormat :#"dok/TV/pays/italie/json/chaine%d.json",indexPath.row];
[httpClient postPath:path parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject)
{resultofData = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:nil];
NSString *valuKey=[NSString stringWithFormat :#"chaine%d",indexPath.row];
NSArray *testArray =[resultofData valueForKey:valuKey];
cd.categ=[[NSArray alloc]initWithArray:testArray];
cd.categArray=[[NSArray alloc]initWithArray:self.categoryArray];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[[UIAlertView alloc]initWithTitle:#":( " message:#"Pas d'internet !" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil]show];
// NSLog(#"[HTTPClient Error]: %#", error.localizedDescription);
}];
[self.navigationController pushViewController:cd animated:YES];
[tableView cellForRowAtIndexPath:indexPath].selected = NO;
}
and in ProgViewController :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
cprogCell *cell = [tableView dequeueReusableCellWithIdentifier:#"identifproecell"];
cell.heure.text = [[_categ objectAtIndex:indexPath.row]objectForKey:#"horaire_programme"];
cell.type.text = #"";
cell.desc.text = [[_categ objectAtIndex:indexPath.row]objectForKey:#"nom_programme"];
return cell;
}
You need to reload the UITableView once your asynchronous request has completed. Currently you push the UIViewController onto the stack and then datasource for it gets set later on, so the cell's only get loaded once you scroll.
To fix this, after you set the cd.categ and cd.categArray, make sure you either call [cd.tableView reloadData], or in the setters for categ or categArray call [self.tableView reloadData]
Too late, but I think you are handling the data to be displayed in the wrong place. For a period of time the user experience is to see a blank screen with no data.
It would be better to move all the data into the controller being pushed. So in your select, create the controller and set the data to be fetched as a property. In the ProgViewController, when inside viewDidAppear start the fetch and activate an activity indicator showing that it is busy. On success or failure stop the activity indicator. On success, call self.tableView.reloadData. On failure, raise the alert and pop the controller.
This way everything is being encapsulated and handled in the right place with the UI updating itself as its internal data state changes.
If you do the fetch from within the initial controller, then you should show its busy and only push the new controller once you have a result for it to display IMHO.
I have an iOS app using CoreData and Restkit 0.20. The main view controller is a collection view that is populated by a request to my server using Restkit. The request to the server returns an image and title to be placed in each cell. If a user selects a cell I need to make a second request to the server for the details about the chosen cell.
I have spent the evening attempting to accomplish this in the prepareForSegue method. The problem is I can't seem to figure out how to get the results from the request that takes place in prepareForSegue.
Here is the prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"newsDetail"]) {
// I USE THIS PORTION TO GET DATA THAT IS NEEDED FOR THE GET REQUEST TO THE SERVER AND TO PASS SOME ADDITIONAL ITEMS FROM THE FIRST REQUEST TO THE DETAIL VIEW CONTROLLER
NSIndexPath *indexPath = [[self.collectionView indexPathsForSelectedItems] lastObject];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
NSString *articleID =[object valueForKey:#"gistID"];
NSString *personID = [object valueForKey:#"userID"];
// I USE THIS LINE TO ASSEMBLE THE STRING FOR getObjectsAtPath BELOW
NSString *getArticle =[NSString stringWithFormat:#"/rest/article/getArticleForView?aid=%#&pid=%#&ip=255.255.255.0",articleID,personID];
//HERE I MAKE THE CALL TO THE SERVER USING RESTKIT METHODS
[[RKObjectManager sharedManager] getObjectsAtPath:getArticle parameters:nil success:nil failure:nil];
[[segue destinationViewController] setDetailItem:object];
}
}
Here is how I load it in the detail view controller.
- (void)configureView
{
if (self.detailItem) {
self.newsDetailText.text = [[self.detailItem valueForKey:#"fullArticle"]description];
NSURL *photoURL =[NSURL URLWithString:[self.detailItem valueForKey:#"imageUrl"]];
NSData *photoData = [NSData dataWithContentsOfURL:photoURL];
self.newsDetailImageView.image =[UIImage imageWithData:photoData];
}
}
It will segue properly and the image (which was already persisted from the first request) will be loaded but the text (which was retrieved from the request at segue) will not. In fact the valueForKey fullArticle is null.
It's probably pretty obvious I'm a rookie based on the question. With that in mind code snippets are VERY helpful. Thanks!
Since you are doing an asynchronous request, the data you wanna show are being arrived after the destinationViewController actually appeared. So, you should pass a success block to your request and update your view inside it. Something like this:
UIViewController *viewController = [segue destinationViewController];
[[RKObjectManager sharedManager] getObjectsAtPath:getArticle parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
if (!operation.error) {
viewController.newsDetailText.text = mappingResult.firstObject[#"fullArticle"];
}
} failure:nil];
I have an application that retrieves json (employees workschedules) from a web service using AFNetworking and displays them in a table view.
I have my webservice class that takes care of doing the request and once it is done, it stores these data into coredata (I have an another issue here, being that I use magicalRecord and the data does not persist, and I don't understand why) and then calls back its delegate (my tableViewController) telling it it's done, so this can load the workschedules into the cells.
WebServiceClient.m
NSURL *url = [NSURL URLWithString:stringUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
NSArray *workSchedules = [[[NSSet alloc] initWithArray:JSON] allObjects];
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
Workschedule *workscheduleEntity = nil;
NSError *error = nil;
for (NSDictionary *web_workschedule in workSchedules)
{//Inside this method I create other entities that will hydrate my workschedule entity, and it is done using the MR_CreateInContext
workscheduleEntity = [Workschedule workScheduleFromJSONDictionary:web_workschedule withError:&error];
[context MR_save];
}
if([self.delegate respondsToSelector:#selector(workSchedules)]){
[self.delegate workSchedules];
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
LOG_ERROR(2,#"Received an HTTTP %d:", response.statusCode);
LOG_ERROR(2,#"The error was: %#", error);
if([self.delegate respondsToSelector:#selector(workSchedules:)]){
[self.delegate workSchedules:nil];//return error
}}];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];
}
PendingWorkscheduleViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.webServiceClient getMockedWorkSchedulesForEmployee:[NSNumber numberWithInt:1]];
[self workSchedules];
}
-(void)workSchedules
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"pending == YES"];
NSArray *pendingWorkSchedules = [Workschedule MR_findAllWithPredicate:predicate];
self.pendingWorkSchedules = pendingWorkSchedules;
[self.tableView reloadData];
}
My problem is that when i run this while the request is processed the UI is unresponsive (it's a very brief time, but if the request were to increase...) so that if i load the table view and right away try to scroll or click the back button, it just ignores it as it is "frozen". This behavior is on my iphone 4s. On the simulator this works fine and I can't wrap my head around why is that. I tried to call the "[self.webServiceClient getMockedWorkSchedulesForEmployee:[NSNumber numberWithInt:1]];" in a queue using GCD, I tried using performSelectorInBackground: WithObject: etc but still the same (even though with this last method it seemed a little more efficient, but it's an impression and only on the simulator, no changes on the device).
As far as magicalRecord goes I will make separate question.
I would appreciate your help.
Fixed it. The problem is that the success block run on the main thread! (which I did not understand). I just used GCD in the success block with a background queue for processing the data and the main queue to store this data in core data.
As far as magical record issue, i needed to save "nestedContext".
Cheers everyone.