I'm a junior level developer and I'm stuck at this very simple thing which I'm unable to figure out.
I've a class which is AFHTTPRequestOperationManager extended. Means it's #interface apiClient : AFHTTPRequestOperationManager in this class I've all my code which fetch images from Imgur API and using AFNetworking I've parsed the data upto a level where I start getting only the link of the images.Now the rootViewController which is a UICollectionViewController extended. In it's viewDidLoad I send a call to my APIClient and it start moving from methods to methods and finally give me an NSMutableArray which contains the images link I'll use in the UIImageView of my CollectionViewCell.
The Question is I'm using this to send the final links back to my rootViewController i.e. GalleryCollectionViewController
GalleryCollectionViewController *gcvc = [[GalleryCollectionViewController alloc] init];
[gcvc recieveGalleryImagesLinks:galleryImgLinkArr];
The problem is that in gcvc it calls numberOfItemsInSection before it can get any response from the API. So that means the count goes out zero and hence it is not displaying the data. So how can I get it to get the API call from apiClient class first and then make the viewController. I hope I've clearly stated my problem and if there is a need of sharing any more code I'll do it.
UPDATE
After authorization this methods gets the gallery images:
- (void)galleryAlbum:(NSString *)ID {
NSLog(#"ID is %#", ID);
[self GET:[NSString stringWithFormat:#"%#3/gallery/album/%#", self.baseURL,ID] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.galleryData = (NSDictionary *)responseObject;
galleryDataArr = [[NSMutableArray alloc] init];
galleryDataArr = [self.galleryData valueForKey:#"data"];
galleryImagesArr = [[NSMutableArray alloc] init];
galleryImagesArr = [galleryDataArr valueForKey:#"images"];
galleryImgLinkArr = [[NSMutableArray alloc] init];
galleryImgLinkArr = [galleryImagesArr valueForKey:#"link"];
GalleryCollectionViewController *gcvc = [[GalleryCollectionViewController alloc] init];
[gcvc recieveGalleryImagesLinks:galleryImgLinkArr];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failed #2");
}];
}
Here gcvc is receiving the array with images link and then reloading the collection view as:
-(void)recieveGalleryImagesLinks:(NSMutableArray *)array
{
self.imageLinks = [[NSMutableArray alloc] initWithArray:array];
NSLog(#"Array: %#",self.imageLinks);
[self.collectionView reloadData];
}
But it's giving "UICollectionView: must be initialized with a non-nil layout parameter" although the array is not empty.
This line of code below is incorrect:
GalleryCollectionViewController *gcvc = [[GalleryCollectionViewController alloc] init];
[gcvc recieveGalleryImagesLinks:galleryImgLinkArr];
You need to use a blocks or delegate approach here which will pass back the array back to your collectionViewController and reload the collecrionView.
Even your method call from API class seems to be correct for delegate approach but make it a protocal method. This way you need not have to change much. It will be something like below:
[self.delegate recieveGalleryImagesLinks:galleryImgLinkArr
And when are calling API and is waiting for response show activityIndicator for loading data.
Related
I am trying to get artworks or album covers using spotify API. I am using:
NSString *url = #"http://ws.spotify.com/search/1/track.json";
NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
([Utils isEmptyString:_searchBar.text] ? #"music" : _searchBar.text), #"q", nil];
[self sendRequestWith:url params:params method:RequestMethodGET success:^(AFHTTPRequestOperation *operation, id response, NSDictionary *userData) {
NSDictionary *result = (NSDictionary *)response;
if(result){
[_trackList removeAllObjects];
NSArray *tracks = [Utils getDictionaryValue:result by:#[#"tracks"]];
for (NSDictionary *trackData in tracks) {
WPTrack *track = [[WPTrack alloc] initWithSpotifyJSON:trackData];
[_trackList addObject:track];
}
[listViewController updateWithObjects:_trackList];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error, NSDictionary *userData) {
} userData:nil];
The current method I am getting doesn't seem to return the thumbnail of the track. But it returns "href" of the track which I can use to search for the track's image by https://embed.spotify.com/oembed/?url=spotify:track:6bc5scNUVa3h76T9nvpGIH. However, this might be another request which could slow my loading on the UITableView. Is there a better way to do this process together?
It common practice to not include rich media content in a api response as the client will have to wait until everything has been sent which can take a long time. To speed up the process you should parser the information gathered and display that to the user while you have another asynchronously operation using a Block to retrieve the image and display it.
Using Async call with cell example
I need help pulling in two APIs with RestKit.
I have API1 modeled, and pulling in correctly already.
The problem is trying to figure out how to pull API2 in to the ViewController.
Specifically, I already have the model class set up, but in the ViewController where the results of API1 + API2 will display, I can't figure out how to work it into my viewDidLoad.
Thanks!
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// RestKit
NSString *baseURL = #"http://api.firstwebsite.com/v1";
RKObjectManager *manager = [RKObjectManager sharedManager];
if (!manager) {
manager = [RKObjectManager objectManagerWithBaseURLString:baseURL];
manager.client.serviceUnavailableAlertEnabled = YES;
manager.requestQueue.showsNetworkActivityIndicatorWhenBusy = YES;
} else {
manager.client.baseURL = [RKURL URLWithString:baseURL];
}
return YES;
}
WebListViewController.m
#property (strong, nonatomic) NSArray *hArray;
- (void)viewDidLoad
{
[super viewDidLoad];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:
[NSString stringWithFormat:
#"/something/?limit=100&something=%#&something=%#&apikey=xxxx", var1, var2]
usingBlock:^(RKObjectLoader *loader) {
loader.onDidLoadObjects = ^(NSArray *objects){
hArray = objects;
[_tableView reloadData];
};
[loader.mappingProvider setMapping:[Fe mapping] forKeyPath:#"fe"];
loader.onDidLoadResponse = ^(RKResponse *response){
//NSLog(#"BodyAsString: %#", [response bodyAsString]);
};
}];
}
Abstract your view controller (and app delegate) away from knowledge of where the data is coming from - they have no business knowing. The app delegate shouldn't really know anything about any of this. The view controller should know that data exists and that additional data can be requested, but this should be in terms of the internal app data model, not the external model or the source of the data.
So, create a data controller. Usually a singleton. Provide an interface to get / set / request / update the data model based on set criteria. Treat all calls as asynchronous with completion callback blocks.
Internally, this data controller can manage multiple object managers, each with a different base URL , mappings and descriptors, but this is all internal knowledge.
I am using UNIRest to make a call and return a JSON object to my app. I have it returning the proper data as a NSDictionary and it logs our perfect. I am now trying to take that data and display it inside of my view. I cannot use my dictionary outside of the callback.
I have been digging around here on StackOverflow for similar results and posts related to variables. I feel it is a scope issue with it being limited to inside of the callback block.
My header file: (UIViewController)
#property (nonatomic, strong) NSDictionary *tideData;
My implementation:
#interface TideDetailViewController ()
#end
#implementation TideDetailViewController
#synthesize tideData;
- (void)viewDidLoad {
[super viewDidLoad];
// tideData = [[NSDictionary alloc] init];
// location is working, I removed it for testing to call a static string for now
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
[self.locationManager startUpdatingLocation];
NSString *locationQueryURL = #"http://api.wunderground.com/api/XXXXXXXXXXXXX/tide/geolookup/q/43.5263,-70.4975.json";
NSLog(#"%#", locationQueryURL);
[[UNIRest get:^(UNISimpleRequest *request) {
[request setUrl: locationQueryURL];
}] asJsonAsync:^(UNIHTTPJsonResponse *response, NSError *error) {
// This is the asyncronous callback block
self.code = [response code];
NSDictionary *responseHeaders = [response headers];
UNIJsonNode *body = [response body];
self.rawResults = [response rawBody];
// I tried this as self as well
tideData = [NSJSONSerialization JSONObjectWithData:self.rawResults options: 0 error: &error];
// this logs perfectly.
NSLog(#"tideData %#", tideData);
// tried setting it to the instance
//self.tideData = tideData;
}];
// returns null
NSLog(#"tideData outside of call back %#", self.tideData);
// this is where I will be setting label text for now, will refactor once I get it working
// rest of file contents........
I have tried a good amount of items related to scoping, clearly just missing the mark. Any ideas? I have searched setting global variables, etc. Been stuck on this for a bit now.
Thanks,
Ryan
The reason you see nil is because you are logging it too soon: when you call
NSLog(#"tideData outside of call back %#", self.tideData);
the get:asJsonAsync: method has not received the results yet.
You can fix this problem by adding a setter for your property, and adding some special handling to it, like this:
-(void)setTideData:(NSDictionary*)dict {
_tideData = dict;
NSLog(#"tideData outside of call back %#", _tideData);
}
This method will be called from the asynchronous code when you do the tideData = ... assignment.
Try setting the object on main thread:
[self performSelectorOnMainThread:#selector(setTideData:) withObject:[NSJSONSerialization JSONObjectWithData:self.rawResults options: 0 error: &error] waitUntilDone:NO];
- (void)setTideData:(NSDictionary*)dict {
self.tideData = dict;
}
I'm new to iOS development and wrestling with UITableViews.
My problem is that I'm populating my UITableView with data from an external server, but due to multithreading it's not waiting until the data arrives before loading the table view.
My current idea is to reload the table view when the data loads.
Earlier in same class DailyBreakdown.c, I reload the table view with this code:
-(void)viewWillAppear:(BOOL)animated
{
[[self class] getAllActivities];
[super viewWillAppear:animated];
[self makeObjects];
[self.tableView reloadData];
}
So on the callback when my data loads (using Restkit), I try to call [self.tableView reloadData] again, but I get the errors:
Definition of 'struct objc_class' must be imported from module
'ObjectiveC.runtime' before it is required
Implicit conversion of Objective-C pointer type 'Class' to C pointer
type 'struct objc_class *' requires a bridged cast
Here's the method where I return the Activity objects from the API:
+(NSArray *)getAllActivities{
if (allActivities == nil) {
// Load the object model via RestKit
//allActivities = [[NSMutableArray alloc] initWithObjects:#"Workout", #"Baked cake", #"Read for HR", nil];
allActivities = [[NSMutableArray alloc] init];
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager getObjectsAtPath:#"/activities"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//allActivities = [mappingResult array];
allActivities = [NSMutableArray arrayWithArray: [mappingResult array]];
[[self class] makeObjects];
/*** THIS LINE IS THE PROBLEM **/
[self.tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
return allActivities;
}
So, why can't I call [self.tableView reloadData] as before? How would I do this from inside my class method?
Also, is there a better way to accomplish this besides reloading the tableview? Maybe threadlocking so that allActivities doesn't return nil when the view is loaded? Any ideas are welcome.
Thanks!
getAllActivities is a class method, so you can't access properties and instance methods from it. The simplest solution would be to make it an instance method. In my opinion, given your situation (trying to access a tableView property) this change would be the right thing to do.
Another solutions to this problem:
you may add a block argument to getAllActivities that will be called when the call to external service completes successfully; in this block you would reload your table
you may pass your instance's self to getAllActivities and use it to access tableView
One more thing I've noticed - return allActivities; won't contain results from the last RestKit call, because it'll be executed before the call completes.
Your getAllActivities method is defined as a class level method (because it's prefixed with a plus). What you probably meant to do was define an instance level method (with a minus in place of the plus).
-(NSArray *)getAllActivities
The next thing I notice is that you're neither doing anything with the result from the call, nor is the result what you'd expect. Your RKObjectManager's getObjectsAtPath is asynchronous, and will return immediately. Meaning that the value for allActivities will almost always be an empty array. Therefore you can further re-define your method as:
-(void)getAllActivities{
//...
//No return here!
}
And, finally, since your method isn't really "getting" activities at all, you might consider naming it to something like:
-(void)reloadAllActivities
I'm using JSONModel to retrieve data from a simple webservice. I need to get the values of key #"message" into a mutable array.
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
self.dataSource = self;
NSString *conversationid = #"xx";
NSString *callURL = [NSString stringWithFormat:#"http://mydomain.com/inbox_messages.php?conversation=%#", conversationid];
_feed = [[MessageFeed alloc] initFromURLWithString:callURL
completion:^(JSONModel *model, JSONModelError *err)
{
self.messages = [[NSMutableArray alloc]initWithObjects:[_feed.messagesinconversation valueForKey:#"message"], nil];
NSLog(#"messages %#", self.messages);
}];
NSLog(#"test %#", self.messages);
}
The problem I'm experiencing is that while: NSLog(#"messages %#", self.messages); returns all the right data, NSLog(#"test %#", self.messages); returns (null).
The variable is declared in .h of my class as: #property (strong, nonatomic) NSMutableArray *messages;
This is probably a noob question but I'm a beginner and if somebody could give me a pointer in the right direction, I would be very happy.
Your NSLog for self.messages is outside of the completion block. The completion block is called after the data is loaded. The log is called immediately after creating the MessageFeed request. So, of course, the object self.messages is null because the request has not completed.
The solution to this would be to either handle all of your parsing within the completion block, or call another class method to parse the received data.
Your completion handler is being called after your NSLog("test %#", self.messages); is.
Blocks usually happen concurrently and when a certain event has occurred like the completion handler here or sometimes an error handler. By looking at your code you're probably getting something like:
test nil
messages
So your MessageFeed object is being run but it continues through your code and runs the NSLog outside of the completion handler scope first. When your JSON object has downloaded, which happens after, and parses it then runs the completion handler.
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
self.dataSource = self;
NSString *conversationid = #"xx";
NSString *callURL = [NSString stringWithFormat:#"http://mydomain.com/inbox_messages.php?conversation=%#", conversationid];
_feed = [[MessageFeed alloc] initFromURLWithString:callURL //this method takes some time to complete and is handled on a different thread.
completion:^(JSONModel *model, JSONModelError *err)
{
self.messages = [[NSMutableArray alloc]initWithObjects:[_feed.messagesinconversation valueForKey:#"message"], nil];
NSLog(#"messages %#", self.messages); // this is called last in your code and your messages has been has been set as an iVar.
}];
NSLog(#"test %#", self.messages); // this logging is called immediately after initFromURLWithString is passed thus it will return nothing
}