Hi I am pretty new to creating Table View Controllers. I'm having difficulty trying to load data retrieved via Json in the ViewDidLoad section, into a table view controller.
The first thing I noticed is that the array where I eventually put my json data (dataArray) is not available in: numberOfRowsInSection, so I declared it outside viewDidLoad but now see that when I debug the array is empty when it gets to numberOfRowsInSection. I did test the retrieval code beforehand so am sure it works.
I have a couple of questions:
1. why is that dataArray is empty in the other methods?
2. is it fine to load data for displaying in tableviews in ViewDidLoad or should I really be loading this data somewhere else where it is more visible and available for use by the tableview methods?
Here is the code:
NSArray *dataArray = nil;
- (void)viewDidLoad {
[super viewDidLoad];
dataArray = [[NSArray alloc] init];
// Set URL variable with Token
NSString *token = #"99fdf505547d607e65b8b8ff16113337";
NSString *teacherUrl = [NSString stringWithFormat: #"https://covle.com/apps/api/nextdoorteacher/teachers?t=%#",token];
// Set up the session configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
[[session dataTaskWithURL:[NSURL URLWithString:teacherUrl]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSLog(#"response ==> %#", response);
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200){
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&error];
NSLog(#" heres the serialised json ===> %#", jsonData);
// move serialised JSON into Array (as the data contained is in array)
dataArray=(NSArray*)[jsonData copy];
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return dataArray.count;
NSLog(#"Array count is set to ===> %#", dataArray.count);
Any help to understand and fix this would be very much appreciated.
Thanks
For this
NSArray *dataArray=(NSArray*)[jsonData copy];
You are creating a new local variable named dataArray which is NOT the same as the global dataArray which u allocated in viewDidLoad, so just write this
dataArray = (NSArray*)[jsonData copy]; and you will get the array count as expected in numberOfRowsInSection
EDIT:
call [self.tableview reloadData] after dataArray = (NSArray*)[jsonData copy] for calling tableview delegate methods to fire once again!
Related
New to iOS development. So I passed a method with an object address, thinking that I would be altering the same object by doing so:
_posts = [[NSMutableArray alloc]init];
FetchPosts *dataControl = [[FetchPosts alloc]init];
[dataControl accessPosts: _posts];
And the below code receives the passed in object.
-(void)accessPosts: (NSMutableArray *)transition {
//access the posts here.
_readablePosts = transition;
NSURL *url = [NSURL URLWithString: #"private"];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
_jsonPosts = [[NSMutableData alloc]init];
[_jsonPosts appendData: data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_jsonPosts options:NSJSONReadingAllowFragments error:&error];
for(int i = 0; i<jsonArray.count; i++) {
NSDictionary *element = jsonArray[i];
Posts *newPost = [[Posts alloc]init];
newPost.title = element[#"Title"];
newPost.discovery = element[#"Discovery"];
newPost.summary = element[#"Description"];
newPost.contact = element[#"Contact"];
[_readablePosts addObject:newPost];
}
}
From my perspective, everything seems to be in place properly. However, when I return to my original method, the _posts does not hold the correct object as it should. Am I doing something wrong with my pointers here?
You call this method -
[dataControl accessPosts: _posts];
Which invokes
[NSURLConnection connectionWithRequest:request delegate:self];
This is a non-blocking method, so it returns immediately and calls the delegate methods once it has retrieved data to be processed.
If you access _posts as soon accessPosts returns you will be doing so before the data has been retrieved from the network.
Only after connectionDidFinishLoading: has been called can you access the data in _posts
One solution is to use a delegation pattern. Your outer code would set itself as a delegate to the FetchPosts object, with the delegate method being called from connectionDidFinishLoading. Not only will this address your asynchronous issue it avoid potentially unsafe updating of the NSMutableArray and avoids the use of side-effects.
You will need to create an appropriate protocol in FetchPosts.h but then you can use something like -
-(void) requestPosts {
FetchPosts *dataControl = [[FetchPosts alloc]init];
dataControl.delegate=self;
[dataControl accessPosts];
}
-(void) didReceiveNewPosts:(NSArray *)posts {
// Do something with posts
}
FetchPosts.m
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_jsonPosts options:NSJSONReadingAllowFragments error:&error];
for(int i = 0; i<jsonArray.count; i++) {
NSDictionary *element = jsonArray[i];
Posts *newPost = [[Posts alloc]init];
newPost.title = element[#"Title"];
newPost.discovery = element[#"Discovery"];
newPost.summary = element[#"Description"];
newPost.contact = element[#"Contact"];
[_readablePosts addObject:newPost];
}
if ([self.delegate respondsToSelector:#selector(didReceiveNewPosts:)]) {
[self.delegate didReceiveNewPosts:_readablePosts];
}
}
I think that the json is empty, the method didReceiveData is called each time that the device obtain a chunk of data, this method can be called more times during a downloading. If you create a new mutable data and append data is like you are resetting the NSDAta object.
You should create the Json post object in didReceiveresponse.
I have this code, but when I log the mediaDictionaryArray, I get null. Does the receiver array have to be initialized with a value first or can I add objects to an empty array? Does [NSArray array] vs. [[NSArray alloc]init] have anything to do with it?
Adding dictionary from API call that happens i times. Asynch call will return the dictionary - can't be sure if NSMutableArray will work in catchJSONArray since asynch nature of call will make the array of indeterminate size which will make it hard to use later on.
Updated with relevant bit.
for (int i = 0; i<[array count]; i++) {
NSString *getString = array[i];
NSLog(#"getstring %#", getString);
[client GET:getString parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if (httpResponse.statusCode == 200) {
dispatch_async(dispatch_get_main_queue(), ^{
_locationMediaArray = (NSArray*)responseObject[#"data"];
[self catchJSONArray:_locationMediaArray];
then here is method with the array issue
-(void)catchJSONArray:(NSArray*)array{
NSArray *catchJSONArray = [NSArray array];
_mediaDictionaryArray = [catchJSONArray arrayByAddingObjectsFromArray:array];
NSLog(#"mediaDictionaryArray %#", _mediaDictionaryArray);
}
arrayByAddingObjectsFromArray returns a new array containing your objects, as an NSArray can not be changed once created.
If you want to change an existing array, you should be using an NSMutableArray.
The best way you could do this is:
_mediaDictionaryArray=[NSArray arrayWithArray:otherArray];
That will create a new array with the contents of otherArray and assign it to _mediaDictionary.
No need to alloc init your array just pass the refrence of your other array. If
_mediaDictionaryArray is mutable array then use below:-
_mediaDictionaryArray=[array mutableCopy];
If it is non mutable array then use below
_mediaDictionaryArray=[array copy];
This question already has answers here:
[__NSCFArray objectForKey:]: unrecognized selector sent to instance
(3 answers)
Closed 8 years ago.
I am trying to fill a UITableView with results from JSON via a url. Im getting a cryptic error ( cryptic to me since this is my first iOS app ).
here is my code:
#import "VideoListViewController.h"
#import "Videos.h"
#import "JSONLoader.h"
#implementation VideoListViewController{
NSArray *_videos;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Create a new JSONLoader with a local file URL
JSONLoader *jsonLoader = [[JSONLoader alloc] init];
NSURL *url = [NSURL URLWithString:#"https://api.wistia.com/v1/medias.json?api_password=1b75e458de33a9b3f99d33f6bf409a7e145c570a&project_id=kx3rkgrv2w"];
// Load the data on a background queue...
// As we are using a local file it's not really necessary, but if we were connecting to an online URL then we'd need it
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_videos = [jsonLoader locationsFromJSONFile:url];
// Now that we have the data, reload the table data on the main UI thread
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
});
}
#pragma mark - Table View Controller Methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"LocationCell"];
Videos *videos = [_videos objectAtIndex:indexPath.row];
cell.textLabel.text = videos.name;
cell.detailTextLabel.text = videos.id;
cell.imageView.image = [UIImage imageNamed:#"chat_video.png"];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_videos count];
}
#end
It never makes it past the dispatch_async call, it just skips over it and then errors out with this error:
'NSInvalidArgumentException', reason: '-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x8c9d0d0'.
The format of the json returned I noticed does not have a selector for the info I am pulling maybe that is the problem?
Thanks,
Sam
EDITED
Here is the JSONLoader method:
- (NSArray *)locationsFromJSONFile:(NSURL *)url {
// Create a NSURLRequest with the given URL
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
// Get the data
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
// Now create a NSDictionary from the JSON data
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// Create a new array to hold the locations
NSMutableArray *videos = [[NSMutableArray alloc] init];
// Get an array of dictionaries with the key "locations"
NSArray *array = [jsonDictionary objectForKey:#""];
// Iterate through the array of dictionaries
for(NSDictionary *dict in array) {
// Create a new Location object for each one and initialise it with information in the dictionary
Videos *video = [[Videos alloc] initWithJSONDictionary:dict];
// Add the Location object to the array
[videos addObject:video];
}
// Return the array of Location objects
return videos;
}
Most (if not all) Objective-c JSON parsers convert objects to instances of NSDictionary class and arrays to instances of NSArray class.
What happens somewhere in your program is that where it assumes it deals with NSDictionary, it actually has an NSArray. I have no idea how your JSON looks like though, so can't say where it happens. Use breakpoints to find out. The problem could be in that JSONLoader class, so if you made it yourself, it might be good idea to check there. If you still have problems, show us code of locationsFromJSONFile method.
In one of my view controllers I am setting a label based on the "GET" data I receive from a separate NSObject class. Obviously it takes much less time to set the label then it does to fetch the data so the label is always set to nil. How can I insure the label isn't set till the data is done fetching.
This is the method preforming the "getting" in the NSObject class myClass
- (void) doGetURL:(NSString *) urlstring
callBackTarget:(id) target
callBackMethod:(NSString *) method
failedMethod:(NSString *) method_failed
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:urlstring]];
NSLog(#"-- get URL with cookie : [%#] and hash:(%#)", [self cookie], [self modhash]);
if (cookie && [cookie length] > 0)
{
NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:
cookieDomain, NSHTTPCookieDomain,
#"/", NSHTTPCookiePath,
#"reddit_session", NSHTTPCookieName,
cookie, NSHTTPCookieValue,
// [cookie stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], NSHTTPCookieValue,
nil];
NSHTTPCookie *http_cookie = [NSHTTPCookie cookieWithProperties:properties];
NSArray* cookies = [NSArray arrayWithObjects: http_cookie, nil];
NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
[request setAllHTTPHeaderFields:headers];
}
NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self];
NSString *connectionKey = [NSString stringWithFormat: #"%ld", ((intptr_t) connection)];
NSMutableDictionary *dl = [[NSMutableDictionary alloc] init];
[dl setValue:connectionKey forKey:#"connectionKey"];
if (target && method)
{
[dl setValue:target forKey:#"afterCompleteTarget"];
[dl setValue:method forKey:#"afterCompleteAction"];
}
[dl setValue:method_failed forKey:#"failedNotifyAction"];
[connections setValue:dl forKey:connectionKey];
}
That is being called in another method within myClass
- (void)getUserInfo:(NSString*)user
{
NSString *getString = [NSString stringWithFormat:#"%#/user/%#/about.json",server,user];
[self doGetURL:getString callBackTarget:self callBackMethod:#"userInfoResponse:" failedMethod:#"connectionFailedDialog:"];
}
The call back method:
- (void)userInfoResponse:(id)sender
{
NSLog(#"userInfoResponse in()");
NSData * data = (NSData *) sender;
NSError *error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error];
NSDictionary *response = [json objectForKey:#"data"];
//futureLabelStr is a property of myClass
futureLabelStr = [response objectForKey:#"name"];;
}
then the label is set in the View Controller:
- (void)viewDidLoad
{
[myClass getUserInfo:#"some_user"];
myLabel.txt = myClass.futureLabelStr;
}
Please let me know is I need to add more or anything I tried to organize it as best I could but I might have missed something.
You don't want to "halt" your viewController's viewDidLoad, you want to notify it, when
the information changes.
You could do that by either sending a notification when myClass is done and -userInfoResponse: is called (Look at NSNotificationCenter), or implement a delegate pattern in myClass. You could set your viewController as a delegate for myClass and call a delegate method when myClass is finished fetching on viewController that would itself update the label.
Or, looking at your code, you could set your viewController as the receiver of the callback methods with minimal change to your code, even though that is not the best approach because it violates MVC patterns:
[self doGetURL:getString callBackTarget:viewController callBackMethod:#"userInfoResponse:" failedMethod:#"connectionFailedDialog:"];
You would of course need a reference to viewController in myClass and the viewController would need to implement this methods (which is a MVC pattern violation).
Send the data call on a new thread and finish viewDidLoad as normal. Then use NSNotification center from whoever is fetching this (should be a model) to the viewController saying "hey, I got that label for you, come get it and refresh"
Then the VC will just set the label using the data from the model. Check out this link for using NSNotificationCenter stackoverflow.com/questions/2191594/send-and-receive-messages-through-nsnotificationcenter-in-objective-c.
For multithreading read up on grand central dispatch.
I want to create a table view slide menu like FB or Linkedin, I mean dynamically, so I have to make some requests at the same time. I am using AFNetworking. with a custom AFHTTTPClient which its called YPLHTTPClients and it is a AFHTTPClient subclass. In this class I have two methods. SharedClient and initWithBaseURL.
I also want to use enqueueBatchOfHTTPRequestOperationsWithRequests method in this class to return data to the viewController and create the tableView of my menu.
I would like to call a method who uses enqueueBatchOfHTTPRequestOperationsWithRequeststhis function in the client and return a dictionary or something with my information data, instead of do everything in the ViewController like I do here:
NSMutableArray *mutableRequests = [NSMutableArray array];
for (NSString *URLString in [NSArray arrayWithObjects:#"users", #"intProjects", nil]) {
[mutableRequests addObject:[[YPLHTTPClient sharedHTTPClient] requestWithMethod:#"GET" path:URLString parameters:nil]];
}
__block NSDictionary *parsedObject1, *parsedObject2;
[[YPLHTTPClient sharedHTTPClient] enqueueBatchOfHTTPRequestOperationsWithRequests:mutableRequests progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"%lu of %lu Completed", (unsigned long)numberOfCompletedOperations, (unsigned long)totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSError *thisError;
parsedObject1 = [NSJSONSerialization JSONObjectWithData:[[operations objectAtIndex:0] responseData] options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&thisError];
NSLog(#"Completion: %#", parsedObject1 );
parsedObject2 = [NSJSONSerialization JSONObjectWithData:[[operations objectAtIndex:1] responseData] options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:&thisError];
NSLog(#"Completion: %#", parsedObject2 );
I also would like to know, how can I show and Image while I am downloading this data.
Thank you
I'm not entirely sure what you're asking, but you can use something like MBProgressHUD to show a progress bar that's updated in progressBlock (progress = numberOfCompletedOperations / totalNumberOfOperations).