I have a method in my "MasterView" () class that parses .json data from a URL then populates a table view with the information. In order to be more organized and group the method with other needed methods I attempted to move it into another NSOject class but it didn't work; no errors, no exceptions the table view simply doesn't populate.
Here is the original method in the "Master Class"
- (void) fetchPosts:
{
NSError *error;
NSData *responseData = [NSData dataWithContentsOfURL:myURL];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *objects = [[json objectForKey:#"data"] objectForKey:#"children"];
arr = [[NSMutableArray alloc] init];
for (NSDictionary *object in objects) {
NSString *title = [[object objectForKey:#"data"] objectForKey:#"title"];
//Post is just a random NSObject Class
Post *post = [[Post alloc] init];
post.title = title;
[arr addObject:post];
}
NSLog(#"Called");
[self.tableView reloadData];
}
The Edited Method in the other class:
- (void) fetchPosts:(NSURL *)myURL withPostArray:(NSMutableArray*)postArr andTableView: (UITableView*)tableView
{
NSLog(#"CAlled");
NSError *error;
NSData *responseData = [NSData dataWithContentsOfURL:myURL];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *objects = [[json objectForKey:#"data"] objectForKey:#"children"];
postArr = [[NSMutableArray alloc] init];
for (NSDictionary *object in objects) {
NSString *title = [[object objectForKey:#"data"] objectForKey:#"title"];
Post *post = [[Post alloc] init];
post.title = title;
[postArr addObject:post];
}
[tableView reloadData];
}
The original Method that works is called: [self fetchPosts:]; the other is: [MyClass fetchPosts:myUrl withPostArray:arr andTableView:self.tableView];
I edited some information out to make it more readable so please let me know if there is any mistakes.
MyClass.h:
#interface MyClass : NSObject <UITableViewDelegate, UITableViewDataSource>
Setting the datasource in MasterView:
//In ViewDidLoad
_delegate = myClass;
self.tableView.dataSource = _delegate;
self.tableView.delegate = _delegate;
//In .h
#property (strong, nonatomic) MyClass *delegate;
Im getting nothing from the compiler when I call [MyClass fetchPosts:myUrl withPostArray:arr andTableView:self.tableView];
If the table view doesn't populate, then the table view is not getting the needed data through the data source.
It's possible that you didn't set the dataSource of your tableView to the new NSObject you created, or that MasterView is still the dataSource of the tableView.
Also, make sure that this method is actually called and the passed tableView is the one presented in the view.
Edit: You have three solutions:
Assign the data source to the new object you created so it handles updating the table view with data, since it now has the actual data.
Adjust that method to return the parsed data to the MasterView and it calls [self.tableView reloadData]. But this is not really good from MVC's point of view.
The third option requires you to create a UIVieController to handle your MasterView and it should be the dataSource for the table view. The view controller should call the said method from the new object, to retrieve the data and update the table view. i.e. like the 2nd solution, but a view controller will call the method and not the MasterView.
Related
I read a lot of docs about this but I can't really understand how it precisely works.
I would like to save my apps data in JSON format on the disc of the phone.
I have a array of objects of this type:
#interface ObjectA : NSObject
#property (strong, nonatomic) NSMutableArray* names1;
#property (strong, nonatomic) NSMutableArray* names2;
#property (strong, nonatomic) NSMutableArray* names3;
#property (strong, nonatomic) NSMutableArray* names4;
#property (strong, nonatomic) NSString* nameObjectA;
#property (assign) int number;
By using JSONModel, how can I transforme a "NSMutableArray *ObjectA" in a JSON file and after that read this file back in the app.
Thanks.
- (id)initWithJSONDictionary:(NSDictionary *)jsonDictionary {
if(self = [self init]) {
// Assign all properties with keyed values from the dictionary
_nameObjectA = [jsonDictionary objectForKey:#"nameAction"];
_number = [[jsonDictionary objectForKey:#"number"]intValue];
_actions1 = [jsonDictionary objectForKey:#"Action1"];
_actions2 = [jsonDictionary objectForKey:#"Action2"];
_actions3 = [jsonDictionary objectForKey:#"Action3"];
_actions4 = [jsonDictionary objectForKey:#"Action4"];
}
return self;
}
- (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 *actions = [[NSMutableArray alloc] init];
// Get an array of dictionaries with the key "actions"
NSArray *array = [jsonDictionary objectForKey:#"actions"];
// 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
Action *action = [[Action alloc] initWithJSONDictionary:dict];
// Add the Location object to the array
[actions addObject:action];
}
// Return the array of actions objects
return actions;
}
The demo app that comes with JSONModel includes an example how to store your app's data via a JSONMOdel: https://github.com/icanzilb/JSONModel
Check the code in this view controller: https://github.com/icanzilb/JSONModel/blob/master/JSONModelDemo_iOS/StorageViewController.m
The logic is that you can export your model to a json string or json compliant dictionary and then save those to the disc using the standard APIs. Check the code
In ObjectA you define two methods -- toDictionary and initWithDictionary. Roughly:
-(NSDictionary*) toDictionary {
return #{#"names1":names1, #"names2":names2, #"names3":names3, #"names4":names4, #"nameObjectA":nameObjectA, #"number":#(number)};
}
- (id) initWithDictionary:(NSDictionary*) json {
self = [super init];
if (self) {
self.names1 = json[#"names1];
... etc
self.nameObjectA = json[#"nameObjectA"];
self.number = json[#"number"].intValue;
}
return self;
}
Run the dictionary created by toDictionary through NSJSONSerialization to produce an NSData and write that to a file. To read, fetch the NSData from the file, run back through NSJSONSerialization, and use initWithDictionary.
Of course, this assumes that the contents of your dictionaries are "JSON legal" -- strings, numbers, NSArrays, or other NSDictionarys.
And, if the arrays/dictionaries being initialized are mutable, one should specify the "MutableContainers" option on NSJSONSerialization.
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.
Dear StackOverflow users,
First Post so will try my best!
I have a simple JSON file which looks like this(I won't include all of it because it's too long):
{
"guidelines": [
{
"title": "Editorial - Doporučené postupy",
"guidelinepath": "1 - Editorial"
},
{
"title": "Preambule",
"guidelinepath": "1 - Preambule"
},
{
"title": "Zásady dispenzární péče ve fyziologickém těhotenství",
"guidelinepath": "1- Z"
},
{
"title": "Provádění screeningu poruch glukózové tolerance v graviditě",
"guidelinepath": "2"
}]
}
With this data, I managed to populate a tableView(that is, the JSON parsed correctly in the command line output), hurray for me. Now what I would like to do is to detect the tableView cell which has been tapped and direct to the guidelinepath JSON object which is related to that title(this will lead to a text file which will populate a text view).I've tried a number of different solutions but they have resulted in (null).
The following is the incomplete code which I have managed to complete and which is error free.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showGuideline"]) {
NSLog(#"seguehasbeenselected");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSLog(#"%# is the indexpath", indexPath);
}
}
I have tried to research the question thoroughly and tried to help myself with the following answers:
Using indexPath.row to get an object from an array
getting json object and then assign to uitable view
But they couldn't really answer my question somehow.Any help would be really appreciated!
For those who would like to get more information about how the JSON was parsed, the following is the code:
{
[super viewDidLoad];
NSString *jsonFilePath = [[NSBundle mainBundle] pathForResource:#"postupy" ofType:#"json"];
NSData *jsonData = [NSData dataWithContentsOfFile:jsonFilePath];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
NSLog(#"%#",dataDictionary);
self.guidelines = [dataDictionary objectForKey:#"guidelines"];
self.guidelineFiles = [dataDictionary objectForKey:#"guidelinepath"];
}
Declaration of guidelineFiles and guidelines:
#import <UIKit/UIKit.h>
#interface PostupyTableViewController : UITableViewController
#property (nonatomic, strong) NSArray *guidelines;
#property (nonatomic, strong) NSArray *guidelineFiles;
#end
-- FINAL SOLUTION --
I edited the prepareForSegue method as follows and it now works perfectly:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showGuideline"]) {
NSLog(#"seguehasbeenselected");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSLog(#"%# is the indexpath", indexPath);
NSDictionary *item = [self.guidelines objectAtIndex:indexPath.row];
NSString * path = [item objectForKey :#"guidelinepath"];
NSLog(#"%# is the path",path);
PostupyDetailViewController *pdwc = (PostupyDetailViewController *)segue.destinationViewController;
pdwc.guidelineChosen = path;
}
}
From your json it seems that [dataDictionary objectForKey:#"guidelines"]; returns an NSArray. So access to the right item just use:
NSDictionary *item = [self.guidelines objectAtIndex:indexPath.row];
NSString *path = [item objectForKey:#"guidelinepath"];
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 am using a helper class in my app, to access a database and return an array of 5 objects, which I then assign to a property in my view controller.
In my view controller I call it like so:
- (void)viewDidLoad
{
[super viewDidLoad];
DatabaseHelper *helper = [[DatabaseHelper alloc] init];
self.trailersArray = [helper retrieveTrailers];
// Set trailer for first round
self.trailer = [self.trailersArray objectAtIndex:0];
// Prepare audio player
[self prepareToPlay];
// Swoop film screen in
[self swoopFilmScreenInAndAddPlayButton];
// Fade title in
[self fadeInTitleScreen];
// Initialise button array and set buttons to hidden
self.buttonOutlets = [NSArray arrayWithObjects:self.button1, self.button2, self.button3, self.button4, nil];
[self hideButtons];
// Initialise rounds
self.round = [NSNumber numberWithInt:-1];
// Initialise score which will also update graphics
[self initialiseScore:self.round];
self.scoreInteger = 0;
// Start first round
self.round = [NSNumber numberWithInt:0];
NSLog([self.trailersArray description]);
// User will trigger playing with Play IBAction
// User will trigger stop playing with Stop Playing IBaction
}
My problem is that once viewDidLoad is finished, the helper object seemingly disappears, as do its objects, and my self.trailersArray ends up pointing at nothing.
How do I fix this? Have tried deep copying, and using a retain attribute on the property but not working.
I can't use a class method because it ruins my helper object database methods but I am intrigued as to how class methods get around this memory problem?
Thanks for any help.
Alan
EDIT: As requested, code for retrieveTrailers below:
-(NSArray *)retrieveTrailers
{
// Get list of mp3 files in bundle and put in array
NSString *bundleRoot = [[NSBundle mainBundle] bundlePath];
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *dirContents = [fm contentsOfDirectoryAtPath:bundleRoot error:nil];
NSPredicate *fltr = [NSPredicate predicateWithFormat:#"self ENDSWITH '.mp3'"];
NSArray *onlyMP3s = [dirContents filteredArrayUsingPredicate:fltr];
NSMutableArray *arrayOfTrailersWithMP3s = [[NSMutableArray alloc] init];
// Query database for objects where (unique id) = (mp3 file title)
for(NSString *string in onlyMP3s)
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Trailer"];
NSString *stringWithNoFileExtension = [string stringByReplacingOccurrencesOfString:#".mp3" withString:#""];
request.predicate = [NSPredicate predicateWithFormat:#"unique = %#", stringWithNoFileExtension];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *array = [[self managedObjectContext] executeFetchRequest:request error:nil];
Trailer *trailer = [array lastObject];
NSLog([NSString stringWithFormat:#"Trailer Available:%#", trailer.title]);
if(trailer)
{
[arrayOfTrailersWithMP3s addObject:trailer];
}
}
// Choose five of the trailers at random and return
NSMutableArray *fiveRandomSelectedTrailers = [[NSMutableArray alloc] init];
int numberOfAvailableTrailers = [arrayOfTrailersWithMP3s count];
for(int i=0;i<5;i++)
{
int rand = (arc4random() % (numberOfAvailableTrailers));
Trailer *trailer = [arrayOfTrailersWithMP3s objectAtIndex:rand];
NSLog([NSString stringWithFormat:#"Trailer Chosen:%#", trailer.title]);
[fiveRandomSelectedTrailers addObject:trailer];
[arrayOfTrailersWithMP3s removeObject:trailer];
numberOfAvailableTrailers --;
}
return fiveRandomSelectedTrailers;
}
If that really is you viewDidLoad code what you are doing is creating a local object of your helper class which is then out of scope once the function has completed.
If you have a retained property of the helper class, you don't need the declaration: try doing it this way
In your .h file have a line like this:
#property(retain, nonatomic) DatabaseHelper *helper;
In your .m file make sure you have:
#synthesize helper;
In your viewDidLoad:
self.helper = [[DatabaseHelper alloc] init];
self.trailersArray = [self.helper retrieveTrailers];
This way you are creating an object of your helper class and assigning it to property, instead of creating a local variable. And, as you can see, you can use the property object of your helper class when you want to send it messages.
I'm assuming you are using MRC instead of ARC.