I am having a hard time populating the tableview cells with json feed dates field. I think it has do to with the way I am getting the dates
NSMutableArray *datesArray = [[NSMutableArray alloc] init];
for (NSDictionary *tempDict in json){
[datesArray addObject:[tempDict objectForKey:#"date"]];
}
Please assist if you can. I have gone through everything I can think of (still learning).
.h file
#import <UIKit/UIKit.h>
#interface AvailabilityViewController : UIViewController <UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource>
{
NSDate *appointmentdate;
UIActionSheet *dateSheet;
UITextField *mydatetextfield;
UILabel *pastDateLabel;
NSArray *json;
}
//-(IBAction)getDataFromJson:(id)sender;
#property (strong, nonatomic) IBOutlet UITextField *mydatetextfield;
#property (nonatomic, retain) NSDate *appointmentdate;
#property (strong, nonatomic) IBOutlet UILabel *pastDateLabel;
#property (strong, nonatomic) IBOutlet UITableView *_tableView;
#property (nonatomic, retain) NSArray *json;
//-(void)setDate;
-(void)dismissDateSet;
-(void)cancelDateSet;
#end
.m file
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
//NSLog(#"string is %#", responseData);
self.json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSLog(#"string is %#", responseData);
if ([json isKindOfClass:[NSArray class]]) {
NSLog(#"its an array");
} else if ([json isKindOfClass:[NSDictionary class]]) {
NSLog(#"its a dictionary");
} else if ([json isKindOfClass:[NSString class]]) {
NSLog(#"its a string");
} else if ([json isKindOfClass:[NSNumber class]]) {
NSLog(#"its a number");
} else if ([json isKindOfClass:[NSNull class]]) {
NSLog(#"its a null");
} else if (json == nil){
NSLog(#"nil");
}
//NSArray* latestLoans = [json objectForKey:#"date"]; //2
NSMutableArray *datesArray = [[NSMutableArray alloc] init];
for (NSDictionary *tempDict in json){
[datesArray addObject:[tempDict objectForKey:#"date"]];
}
NSLog(#"this is your datesArray %#", datesArray);
}
- (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 self.json.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [[self.json objectAtIndex:indexPath.row] objectForKey:#"date"];
return cell;
//[_tableView reloadData];
}
Here is my NSLog of datesArray
2012-08-21 10:09:39.303 GBSB[1409:15b03] this is your datesArray (
"2012-08-13 12:00:00",
"2012-08-13 10:00:00",
"2012-08-13 13:00:00"
Here is what the viewDidLoad looks like
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
kLatestKivaLoansURL];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
}
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
//NSLog(#"string is %#", responseData);
self.json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSLog(#"string is %#", responseData);
if ([json isKindOfClass:[NSArray class]]) {
NSLog(#"its an array");
} else if ([json isKindOfClass:[NSDictionary class]]) {
NSLog(#"its a dictionary");
} else if ([json isKindOfClass:[NSString class]]) {
NSLog(#"its a string");
} else if ([json isKindOfClass:[NSNumber class]]) {
NSLog(#"its a number");
} else if ([json isKindOfClass:[NSNull class]]) {
NSLog(#"its a null");
} else if (json == nil){
NSLog(#"nil");
}
//NSArray* latestLoans = [json objectForKey:#"date"]; //2
NSMutableArray *datesArray = [[NSMutableArray alloc] init];
for (NSDictionary *tempDict in json){
[datesArray addObject:[tempDict objectForKey:#"date"]];
}
NSLog(#"this is your datesArray %#", datesArray);
NSLog(#"this is the json %#", self.json);
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
You are creating a local variable called json in your fetchedData method and putting the parsed response in there. However, because this is a local variable it ceases to exist once your exit from the method.
Instead, what you should do is to put the parsed response data into your viewController's json #property which you declare in the .h file. To do this, make this change to your fetchedData: method:
self.json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
Then in your cellForRowAtIndexPath: you can pull out the data like this:
cell.textLabel.text = [[self.json objectAtIndex:indexPath.row] objectForKey:#"date"]
Also your numberOfRowsInSection: should return:
return self.json.count;
EDIT:
More explanation about local variables vs. ivars vs. properties...
When you declare this in your view controller's #interface in the .h file:
NSArray *json;
you are creating in instance variable (ivar) for your class. Whenever an instance of your class is instantiated it will have a member variable named json that you can access within the methods of your class.
When you declare a property like this:
#property (nonatomic, retain) NSArray *json;
and a matching #synthesize in the implementation file:
#synthesize json;
the compiler auto generates a setter and getter method for you, so you can then use these methods:
NSArray *theArray = [self json]; // getter
[self setJson:newArray]; // setter
You can do the same in modern Objective-C using dot notation:
NSArray *theArray = self.json; // getter
self.json = newArray; // setter
Your property ends up being backed by an ivar, which is by default named the same of the property and will be autogenerated for you if it doesn't exist. (You can also specify the name of the backing ivar in the #synthesize statement, and you'll often see people using ivar names that start with an underscore to help keep straight what is the ivar name and what is the property name, but I won't go into that further here)
Your object's properties can be accessed from other classes, whereas your object's ivars cannot.
But back to your question. In addition to your ivar and property, you have created a local variable, also named json in your fetchedData: method. This variable, because you declare it within the body of the method, will only exist until the method finishes, at which time it will be deallocated and the data contained will be lost if not retained elsewhere. Because you have given your local variable the same name as your ivar, the local variable effective hides the ivar.
Apple does not recommend using ivars directly anyway, but instead doing all access through your class properties (getter and setter methods). That's why I suggested using self.json. It should also fix your problem, since values saved to your property will persist beyond the execution of the method.
Hope that helps some.
It looks like 'json' is an array of NSDictionary's.
You can't do this:
cell.textLabel.text = [json objectAtIndex:indexPath.row];
(i.e. assign an NSDictionary to a UILabel's text field)
but you could do something like:
cell.textLabel.text = [[json objectAtIndex:indexPath.row] objectForKey:#"date"];
Related
I am having trouble getting my UITableView to reload appropriately. My app sometime works and other times it crashes because one of the data elements is not properly stored in an array when the tableView tries to update. Can someone point me out the proper place to reload my tableView please.
- (void)viewDidLoad {
[super viewDidLoad];
[self queryForNewBooks];}
-(void)queryForNewBooks{
_bookNameArray = [[NSMutableArray alloc] init];
_authorNameArray = [[NSMutableArray alloc] init];
_isbnNumberArray = [[NSMutableArray alloc] init];
_bookImageData = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:#"BooksForSale"];
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^( NSArray *objects, NSError *error) {
if (objects.count >=1) {
for (PFObject *object in objects) {
PFFile *imageFile = [object objectForKey:#"image"];
[imageFile getDataInBackgroundWithBlock:^(NSData *result, NSError *error) {
if (!error) {
NSData *data = result;
NSLog(#"HEYYYYYY");
if (data == NULL) {
}
else{
[ _bookImageData addObject:data];
//I have tried placing it here [self.tableView reloadData]
// NSLog(#"%#",[_bookImageData objectAtIndex:0]);
}
}
}
];
NSDictionary *bookNameDictionary = object[#"nameOfBook"];
NSDictionary *authorNameDictionary = object[#"Author"];
NSDictionary *bookImageDictionary = object [#"image"];
NSDictionary *isbnNumberDictionary = object [#"isbnNumber"];
NSString *objectID = [object objectId];
if (bookNameDictionary != NULL){
NSLog(#"Yo bro here is the book name %#",bookNameDictionary);
[_bookNameArray addObject:bookNameDictionary];
NSLog(#"number: %#", bookNameDictionary);
}
if (bookNameDictionary == NULL) {
[_bookNameArray addObject:#""];
NSLog(#"Blank space");
}
if (authorNameDictionary != NULL) {
[_authorNameArray addObject:authorNameDictionary];
// [_tableData addObject:ft];
NSLog(#"Author Name : %#",_authorNameArray);
// NSLog(#"the table data is %#",_tableData);
}
if (authorNameDictionary == NULL) {
[_authorNameArray addObject:#""];
NSLog(#"Blank space");
}
if (isbnNumberDictionary != NULL){
NSLog(#"Yo bro here is the isbn %#",isbnNumberDictionary);
[_isbnNumberArray addObject:isbnNumberDictionary];
NSLog(#"number: %#", isbnNumberDictionary);
//[self.tableview reloadData];
}
if (isbnNumberDictionary == NULL) {
[_isbnNumberArray addObject:#""];
NSLog(#"Blank space");
}
/* if (bookImageDictionary !=NULL){
[_bookImageData addObject:bookImageDictionary];
}
if (bookImageDictionary ==NULL){
[_bookImageData addObject:#""];
NSLog(#"Blank Space");
}*/
if (objectID != NULL) {
[_objectIDArray addObject:objectID];
NSLog(#"object id is : %#",objectID);
}
if (objectID ==NULL){
[_objectIDArray addObject:#"blank"];
}
}
}
// code
}];
//I have tried placing it here [self.tableView reloadData]
);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.bookNameArray count];
}
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"TableViewCell";
TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"TableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSLog(#"Here are the book names, %#",_bookNameArray);
cell.bookNameLabel.text = [_bookNameArray objectAtIndex:indexPath.row];
cell.authorNameLabel.text = [_authorNameArray objectAtIndex:indexPath.row];
if ([_bookImageData objectAtIndex:indexPath.row] != NULL ) {
NSLog(#"it seems to work");
UIImage *image = [UIImage imageWithData: [_bookImageData objectAtIndex:indexPath.row]];
cell.bookImageLabel.image = image;
}
else{
NSLog(#"Error");
}
return cell;
}
Updating question:
Is this the proper way to declare a PFimageView?
Can I just drag a UIImageView in the Xib file and then
change its class to PFImageView?
After changing it to a PFImageView should I be able to just link the view to the outlet as normally done?
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import <ParseUI/ParseUI.h>
#interface TableViewCell : UITableViewCell
#property (nonatomic, weak) IBOutlet UILabel *bookNameLabel;
#property (nonatomic, weak) IBOutlet UILabel *authorNameLabel;
#property (nonatomic, weak) IBOutlet PFImageView *bookImageLabel;
#property (nonatomic, weak) IBOutlet UILabel *priceOfBook;
#end
#import <Parse/Parse.h>
#import "TableViewCell.h"
#import <ParseUI/ParseUI.h>
#implementation TableViewCell
#synthesize bookNameLabel =_bookNameLabel;
#synthesize authorNameLabel = _authorNameLabel;
#synthesize bookImageLabel = _bookImageLabel;
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
The part that complicates the code is the need to fetch images using the objects returned by the query. The simplest solution is to omit the image fetching from the query completion handler.
Instead, loop the returned objects, building the arrays (there's room for improvement here, too, but just sticking with the crash part of your problem for now). In the _bookImageData array, don't try to keep images, instead keep the PFFile for each object...
// in the loop of objects, omit getDataInBackground and just do...
[_bookImageData addObject:object[#"image"]];
The answer to the stated question -- where to reload the table -- is after the loop that builds the table datasource.
for (PFObject *object in objects) {
// code to build all of the arrays, omitting getDataInBackground
// ...
}
[self.tableView reloadData];
In your table view cell, replace the image view with a PFImageView, because it can take care of fetching the image data for you (and other useful stuff like cacheing it). Your cellForRowAtIndexPath: will then look like this...
// bookImageLabel must be a PFImageView
// remember, we put the PFFile for each object into the _bookImageData array
cell.bookImageLabel.file = _bookImageData[indexPath.row];
[cell.bookImageLabel loadInBackground];
I have to create a UITableView using the JSON response below ( Array ). I have no code for this yet but would love some direction to how i would split this array to accommodate categories and items on all levels.
{
"result":{
"products":[
{
"id":"4",
"product_code":"PR04",
"title":"Product1",
"franchisee_id":"118"
}
],
"categories":[
{
"id":"8",
"name":"Category1"
},
{
"id":"20",
"name":"Category2",
"products":[
{
"id":"9",
"product_code":"PR07",
"title":Product2,
"franchisee_id":"118"
}
]
}
]
}
}
I want to achieve the following result:
items
Category1 > items
Category2 > items
When a category is clicked it would slide to the products in that category. Would really love some direction on this. Some products will not be in categories. Like the example above.
Well....
You need to parse the JSON file. You can easily google for some tutorials but here is a decent one.
Next you are going to need to setup a UITableView to load the items. another good tutorial on UITableViews
Then you are going to need to learn how to pass data between UIViewControllers. Tutorial.
So your steps in the code will be to:
Parse the JSON to separate all the elements.
Setup a UITableView to display the top level elements.
Create a second UITableViewController to push to after a top level item has been selected.
Setup a custom initializer for the second UITableViewController so you can pass it relevant data from the first view controller where you parsed the JSON.
I'm assuming you were looking for a bunch of code on how to do this, but that's no fun :)
Let me know if you run into any troubles and I will be glad to help.
EDIT:
I know I said I wasn't going to dump code but I have some extra time.
Create an NSObject subclass called ProductObject and make the .h look like this:
#import <Foundation/Foundation.h>
#interface ProductObject : NSObject
#property NSString *productCode, *productTitle, *franchiseId, *productId;
#end
Don't do any thing to the .m
Create another NSObject subclass called CategoryObject and make the .h look like this:
#import <Foundation/Foundation.h>
#interface CategoryObject : NSObject
#property NSString *categoryName, *categoryId;
#property NSArray *products;
#end
Again, don't need to do anything to the .m.
Now, in the class that you want to display the UITableView will the Products and Categories (this is all in the .m, the .h is empty):
#import "ViewController.h"
#import "CategoryObject.h"
#import "ProductObject.h"
#interface ViewController ()
//Hooked in from IB
#property (weak, nonatomic) IBOutlet UITableView *table;
//Our UITableView data source
#property NSMutableDictionary *tableObjects;
#end
#implementation ViewController
/**
Parses a the local JSON file
*/
- (void)parseJSON {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"json"];
//création d'un string avec le contenu du JSON
NSString *myJSON = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:NULL];
NSError *error;
NSDictionary *topLevleJSON = [NSJSONSerialization JSONObjectWithData:[myJSON dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
if (error) {
NSLog(#"Error serializing JSON: %#", error.localizedDescription);
return;
}
NSArray *products = topLevleJSON[#"products"];
NSArray *categories = topLevleJSON[#"categories"];
//Use a NSDictonary so that it contains an NSArray of ProductObjects for the "Products" key, and an array of CategoryObjects for the "Category" key.
self.tableObjects = [NSMutableDictionary new];
//Parse all the products
NSMutableArray *productsInJSON = [NSMutableArray new];
[products enumerateObjectsUsingBlock:^(NSDictionary *productObject, NSUInteger idx, BOOL *stop) {
ProductObject *product = [self createProductObjectFromDictionary:productObject];
[productsInJSON addObject:product];
}];
//Set the array of ProductObjects for the key #"Products"
[self.tableObjects setObject:productsInJSON forKey:#"Products"];
//Parse all the categories
NSMutableArray *categoriesInJSON = [NSMutableArray new];
[categories enumerateObjectsUsingBlock:^(NSDictionary *categoryObject, NSUInteger idx, BOOL *stop) {
CategoryObject *category = [self createCategoryObjectFromDictionary:categoryObject];
[categoriesInJSON addObject:category];
}];
//Set the array of CategoryObjects for key #"Categories"
[self.tableObjects setObject:categoriesInJSON forKey:#"Categories"];
[self.table reloadData];
}
/**
Creates a ProductObject from an NSDictonary.
#param dictionary The dictonary describing the Product parsed from JSON
#return A pretty formatted ProductObject
*/
- (ProductObject*)createProductObjectFromDictionary:(NSDictionary*)dictionary {
ProductObject *product = [ProductObject new];
product.productTitle = dictionary[#"title"];
product.productCode = dictionary[#"product_code"];
product.franchiseId = dictionary[#"franchisee_id"];
product.productId = dictionary[#"id"];
return product;
}
/**
Creates a Category from an NSDictionary
#param dictionary The dictonary describing the Category parsed from JSON
#return A pretty formatted CategoryObject
*/
- (CategoryObject*)createCategoryObjectFromDictionary:(NSDictionary*)dictionary {
CategoryObject *category = [CategoryObject new];
category.categoryId = dictionary[#"id"];
category.categoryName = dictionary[#"name"];
//Check to see if the "products" key exist for the category, if we don't check and just look for it, we will get a crash if it doesn't exist.
if ([[dictionary allKeys] containsObject:#"products"]) {
NSArray *categoryProducts = dictionary[#"products"];
//Parse all the Products for the Category.
NSMutableArray *categoryProductsFormatted = [NSMutableArray new];
[categoryProducts enumerateObjectsUsingBlock:^(NSDictionary *productObject, NSUInteger idx, BOOL *stop) {
ProductObject *product = [self createProductObjectFromDictionary:productObject];
[categoryProductsFormatted addObject:product];
}];
category.products = [NSArray arrayWithArray:categoryProductsFormatted];
}
else {
category.products = nil;
}
return category;
}
#pragma mark -
#pragma mark - UITableView delegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.tableObjects allKeys] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//Get the key for this section
NSString *key = [[self.tableObjects allKeys] objectAtIndex:section];
//Return the number of objects for this key.
return [(NSArray*)[self.tableObjects objectForKey:key] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[self.tableObjects allKeys] objectAtIndex:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellIdentifier"];
}
//Get all the NSArray associated with this section, which will be an array of ProductObjects or an array of CategoryObjects
NSString *key = [[self.tableObjects allKeys] objectAtIndex:indexPath.section];
NSArray *sectionobjects = (NSArray*)[self.tableObjects objectForKey:key];
id object = [sectionobjects objectAtIndex:indexPath.row];
//Set the cell text based on what kind of object is returned
if ([object isKindOfClass:[ProductObject class]]) {
cell.textLabel.text = [(ProductObject*)object productTitle];
}
else if ([object isKindOfClass:[CategoryObject class]]) {
cell.textLabel.text = [(CategoryObject*)object categoryName];
}
return cell;
}
#pragma mark -
#pragma mark - UITableView delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSString *key = [[self.tableObjects allKeys] objectAtIndex:indexPath.section];
NSArray *sectionobjects = (NSArray*)[self.tableObjects objectForKey:key];
id object = [sectionobjects objectAtIndex:indexPath.row];
//They selected a product
if ([object isKindOfClass:[ProductObject class]]) {
ProductObject *product = (ProductObject*)object;
NSLog(#"%#", product.productTitle);
NSLog(#"%#", product.productCode);
NSLog(#"%#", product.productId);
}
//They selected a Category
else if ([object isKindOfClass:[CategoryObject class]]) {
//Check to see if the CategoryObject has any ProductObjects associated with it
if ([(CategoryObject*)object products]) {
//Now you will need to pass array of ProductObjects this along to your next view controller.
NSArray *cateogryProducts = [(CategoryObject*)object products];
//For demonstration purposes, i'll run through and print out all the Products for this Category
[cateogryProducts enumerateObjectsUsingBlock:^(ProductObject *product, NSUInteger idx, BOOL *stop) {
NSLog(#"%#", product.productTitle);
NSLog(#"%#", product.productCode);
NSLog(#"%#", product.productId);
}];
}
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//Start parsing the JSON
[self parseJSON];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
EDIT:
If you are wanting to open and close parts of the table like an accordion, take a look at Apple's same code: Table View Animations and Gestures.
I'm trying to parse a JSON file to my iOS app table view.
When I launch the app I see that it parses the data, but when I begin to scroll the app instantly crashes and gives me this error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull length]: unrecognized selector sent to instance 0x38094a60'
My code:
#import "FirstViewController.h"
#import "YoutubePost.h"
#interface FirstViewController ()
{
NSInteger refreshIndex;
NSArray *title;
NSArray *about;
NSArray *views;
NSArray *rating;
NSArray *votes;
NSArray *content;
}
#end
#implementation FirstViewController
#synthesize tweets;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"Videos", #"Videos");
self.tabBarItem.image = [UIImage imageNamed:#"newtab1"];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.myTableView.separatorColor = [UIColor clearColor];
[self issueLoadRequest];
[self setNeedsStatusBarAppearanceUpdate];
}
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
#pragma mark - Table view data source
- (void)issueLoadRequest
{
// Dispatch this block asynchronosly. The block gets JSON data from the specified URL and performs the proper selector when done.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"my-site.php/file.json"]];
[self performSelectorOnMainThread:#selector(receiveData:) withObject:data waitUntilDone:YES];
});
}
- (void)receiveData:(NSData *)data {
// When we have the data, we serialize it into native cocoa objects. (The outermost element from twitter is
// going to be an array. I JUST KNOW THIS. Reload the tableview once we have the data.
self.tweets = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[self.myTableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"YoutubePost";
YoutubePost *cell = (YoutubePost *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"YoutubePost" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// The element in the array is going to be a dictionary. I JUST KNOW THIS. The key for the tweet is "text".
NSDictionary *temp = [self.tweets objectAtIndex:indexPath.row];
NSDictionary *tweet = [self nullFreeDictionaryWithDictionary:temp];
cell.title.text = [tweet objectForKey:#"title"];
cell.views.text = [tweet objectForKey:#"views"];
return cell;
}
- (NSDictionary *)nullFreeDictionaryWithDictionary:(NSDictionary *)dictionary
{
NSMutableDictionary *replaced = [NSMutableDictionary dictionaryWithDictionary:dictionary];
// Iterate through each key-object pair.
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop) {
// If object is a dictionary, recursively remove NSNull from dictionary.
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *innerDict = object;
replaced[key] = [NSDictionary nullFreeDictionaryWithDictionary:innerDict];
}
// If object is an array, enumerate through array.
else if ([object isKindOfClass:[NSArray class]]) {
NSMutableArray *nullFreeRecords = [NSMutableArray array];
for (id record in object) {
// If object is a dictionary, recursively remove NSNull from dictionary.
if ([record isKindOfClass:[NSDictionary class]]) {
NSDictionary *nullFreeRecord = [NSDictionary nullFreeDictionaryWithDictionary:record];
[nullFreeRecords addObject:nullFreeRecord];
}
else {
if (object == [NSNull null]) {
[nullFreeRecords addObject:#""];
}
else {
[nullFreeRecords addObject:record];
}
}
}
replaced[key] = nullFreeRecords;
}
else {
// Replace [NSNull null] with nil string "" to avoid having to perform null comparisons while parsing.
if (object == [NSNull null]) {
replaced[key] = #"";
}
}
}];
return [NSDictionary dictionaryWithDictionary:replaced];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 397;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString * storyLink = [[tweets objectAtIndex: storyIndex] objectForKey:#"link"];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:storyLink]];
// Spit out some pretty JSON for the tweet that was tapped. Neato.
NSString *formattedJSON = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:[self.tweets objectAtIndex:indexPath.row] options:NSJSONWritingPrettyPrinted error:nil] encoding:NSUTF8StringEncoding];
NSLog(#"tweet:\n%#", formattedJSON);
}
#end
Before I installed the new Xcode 5 I didn't get this error. Can someone help me?
Thanks.
What I've been doing myself to avoid this when parsing JSON results is replacing each instance of NSNull with a null string (#""), using the following method:
+ (NSDictionary *)nullFreeDictionaryWithDictionary:(NSDictionary *)dictionary
{
NSMutableDictionary *replaced = [NSMutableDictionary dictionaryWithDictionary:dictionary];
// Iterate through each key-object pair.
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop) {
// If object is a dictionary, recursively remove NSNull from dictionary.
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *innerDict = object;
replaced[key] = [NSDictionary nullFreeDictionaryWithDictionary:innerDict];
}
// If object is an array, enumerate through array.
else if ([object isKindOfClass:[NSArray class]]) {
NSMutableArray *nullFreeRecords = [NSMutableArray array];
for (id record in object) {
// If object is a dictionary, recursively remove NSNull from dictionary.
if ([record isKindOfClass:[NSDictionary class]]) {
NSDictionary *nullFreeRecord = [NSDictionary nullFreeDictionaryWithDictionary:record];
[nullFreeRecords addObject:nullFreeRecord];
}
else {
if (object == [NSNull null]) {
[nullFreeRecords addObject:#""];
}
else {
[nullFreeRecords addObject:record];
}
}
}
replaced[key] = nullFreeRecords;
}
else {
// Replace [NSNull null] with nil string "" to avoid having to perform null comparisons while parsing.
if (object == [NSNull null]) {
replaced[key] = #"";
}
}
}];
return [NSDictionary dictionaryWithDictionary:replaced];
}
Of course, this relies on the JSON return format being a dictionary, but you could easily modify it to accommodate other data types if you replaced all the parameter types with id and performed class checks.
--Edit--
If this is the only place you'll be using JSON, then change
NSDictionary *tweet = [self.tweets objectAtIndex:indexPath.row];
to
NSDictionary *temp = [self.tweets objectAtIndex:indexPath.row];
NSDictionary *tweet = [NSDictionary nullFreeDictionaryWithDictionary:temp];
Note that I have nullFreeDictionaryWithDictionary as an Objective-C category for the NSDictionary class. You could probably just add that to your view controller's implementation file if you weren't going to use this method anywhere else.
I'm getting this JSON:
{
"timestamp": "2013-05-03T22:03:45Z",
"resultsOffset": 0,
"status": "success",
"resultsLimit": 10,
"breakingNews": [],
"resultsCount": 341,
"feed": [{
"headline": "This is the first headline",
"lastModified": "2013-05-03T21:33:32Z",
"premium": false,
"links": {
"api": {
And use this to load it in a UITableView:
#property (strong, nonatomic) NSArray *headlinesArray;
- (void)viewDidLoad
{
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[NSString stringWithFormat:#"/now/?leafs=%#&teas=%#&apikey=xxxxx", leafAbbreviation, teaID] usingBlock:^(RKObjectLoader *loader) {
loader.onDidLoadObjects = ^(NSArray *objects){
premiumArray = objects;
[_tableView reloadData];
};
[loader.mappingProvider setMapping:[Feed mapping] forKeyPath:#"feed"];
loader.onDidLoadResponse = ^(RKResponse *response){
//NSLog(#"BodyAsString: %#", [response bodyAsString]);
};
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"standardCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Feed *feedLocal = [headlinesArray objectAtIndex:indexPath.row];
NSString *headlineText = [NSString stringWithFormat:#"%#", feedLocal.headline];
cell.textLabel.text = headlineText;
return cell;
}
Headline class model:
#property (strong, nonatomic) NSString *headline;
#property (strong, nonatomic) Links *linksHeadline;
Is there any way to check if premium is true in the JSON, to not show the headline in the UITableView?
EDIT 1
I added #property (strong, nonatomic) NSArray *premiumArray; which is pulling in the correct data associated with premium, so now I just need help looking thru that array for links that say TRUE so my UITableView won't show any headlines that premium = TRUE.
EDIT 2
I posted the viewDidLoad code above.
EDIT 3
Feed.h
#property (nonatomic, strong) NSString *headline;
#property (nonatomic, strong) NSString *premium;
Feed.m
+ (RKObjectMapping *)mapping {
RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[self class] usingBlock:^(RKObjectMapping *mapping) {
[mapping mapKeyPathsToAttributes:
#"headline", #"headline",
#"premium", #"premium",
nil];
}];
return objectMapping;
}
EDIT
I added this per some answers, but still couldn't get it working, any thoughts?
#property (strong, nonatomic) NSArray *premiumArray;
#property (strong, nonatomic) NSMutableArray *myMutable;
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[NSString stringWithFormat:#"/now/?leagues=%#&teams=%#&apikey=5qqpgrsnfy65vjzswgjfkgwy", leagueAbbreviation, teamID] usingBlock:^(RKObjectLoader *loader) {
loader.onDidLoadObjects = ^(NSArray *objects){
//sports = objects;
premiumArray = objects;
[_tableView reloadData];
};
[loader.mappingProvider setMapping:[Feed mapping] forKeyPath:#"feed"];
loader.onDidLoadResponse = ^(RKResponse *response){
//NSLog(#"BodyAsString: %#", [response bodyAsString]);
};
}];
self.myMutable = [[premiumArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"premium = YES"]] mutableCopy];
You will need to create some sort of UITableView data source. Having the UITableView handle this is much more difficult than simply setting up a data structure and then pass that data in to the UITableView data source.
A NSMutableArray would work just fine for what you need done. Whatever JSON parser toolkit you are using, you are likely given an arrayed response, which looks like is stored in headlinesArray, each containing the example code above.
You simply need to enumerate through headlinesArray and IF the [post objectForKey:#"premium"] == TRUE then add it to the NSMutableArray.
Place all of this in the viewDidLoad so that it is processed before the UITableView is built and then in the TableView you simply need to access that newly built array.
.h
#interface YourClass: YourClassSuperclass
{
NSMutableArray *a;
}
.m
//In ViewDidLoad
a = [NSMutableArray array]; //Allocs and Inits a new array.
for (Feed *f in headlinesArray) //For all feeds in the headlines array. (f is the local var)
{
//check if premium == TRUE. If yes, add to a.
}
//Then in your data source methods you just use the array named 'a'
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"standardCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Feed *feedLocal = [a objectAtIndex:indexPath.row]; //Replaced headlinesArray with a
NSString *headlineText = [NSString stringWithFormat:#"%#", feedLocal.headline];
cell.textLabel.text = headlineText;
return cell;
}
In your table view data source, you will want an NSMutableArray. When you get the data, use this:
NSArray *someFeedArray = ...;
self.mutableArray = [[NSMutableArray alloc] init];
for (NSDictionary *dict in someFeedArray)
{
BOOL isPremium = [[[(NSArray *)dict[#"feed"] objectAtIndex:0] objectForKey:"premium"] boolValue] isEqualToString:#"true"]; //Assuming stored as string
if (!isPremium) [self.mutableArray addObject:dict];
}
In your numberOfRowsInSection method, you should do this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.mutableArray.count;
}
And you're done.
I have a uitableview with a list of items populated from a JSON file located locally within the app. Everything works as far as getting the list to the table and the multiple selection of items which when selected (or deselected) are then saved to a nsmutablearray.
The problem is when the user leaves the view and returns and selects another item (or deselects a currently selected item). At this point the mutable array is then empty.
I'm not sure if the nsuserdefaults saving of the mutable array is the problem. it saves it fine but then when the view reappears (the mutable array's value is fine at this point) and the user touches a table row the array is null once more.
my .h file:
#interface CategoriesViewController : UITableViewController {
NSMutableArray *_selectedItems;
NSString *filePath;
NSString *string;
}
// arForTable array will hold the JSON results from the api
#property (nonatomic, retain) NSArray *arForTable;
#property (nonatomic, retain) NSMutableArray *categorySelected;
#property (nonatomic, retain) NSString *jsonStringCategory;
#property(nonatomic, retain) UIView *accessoryView;
#end
my .m file:
#implementation CategoriesViewController
#synthesize arForTable = _arForTable;
- (void)viewDidLoad
{
[super viewDidLoad];
self.categorySelected = [[NSMutableArray alloc] init];
[self reloadMain];
// assignment reference so don't release
_selectedItems = [(AppDelegate *)[[UIApplication sharedApplication] delegate] selectedCategories];
self.tableView.hidden = NO;
}
-(void) reloadMain {
// countrySaved value from NSUserDefaults
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSString *countryString = [defaults stringForKey:#"selectedCountryTableString"];
NSString *cityString = [defaults stringForKey:#"selectedCityTableString"];
NSLog(#"countrystring from category is %#", countryString);
NSLog(#"citystring from category is %#", cityString);
// getting path to the file
if ([defaults stringForKey:#"selectedCountryTableString"] == NULL) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesit" ofType:#"json"];
} else if ([countryString isEqualToString:#"UK"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesuk" ofType:#"json"];
} else if ([countryString isEqualToString:#"Italy"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesit" ofType:#"json"];
} else if ([countryString isEqualToString:#"Spain"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categorieses" ofType:#"json"];
} else if ([countryString isEqualToString:#"Brazil"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesbr" ofType:#"json"];
}
NSString *fileContent = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
//NSLog(#"File content: %#", fileContent);
// creating new parser
SBJSON *parser = [[SBJSON alloc] init];
// parsing the first level
NSDictionary *data = (NSDictionary *) [parser objectWithString:fileContent error:nil];
NSDictionary *menu = (NSDictionary *) [data objectForKey:#"menu"];
#ifdef DEBUG
NSLog(#"menu is %#",menu);
#endif
NSMutableArray *itemsTMP = [[NSMutableArray alloc] init];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:nil];
// NSLog(#"results File test %#",dict);
itemsTMP = [dict objectForKey:#"results"];
// NSLog(#"itemsTMPitemsTMP File test %#",itemsTMP);
self.arForTable = [itemsTMP copy];
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.arForTable count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell.textLabel setFont:[UIFont fontWithName: #"Asap-Bold" size: 14.0f]];
[cell.detailTextLabel setFont:[UIFont fontWithName: #"Asap-Bold" size: 14.0f]];
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRed:204.0/255.0 green:56.0/255.0 blue:55.0/255.0 alpha:1];
}
UIImageView *cellAccessoryImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon-tick.png"]] ;
UIImageView *cellAccessoryNoneImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#""]] ;
if([_selectedItems containsObject:indexPath]){
cell.accessoryView = cellAccessoryImageView;
} else {
cell.accessoryView = cellAccessoryNoneImageView;
}
// Get item from tableData
NSDictionary *item = (NSDictionary *)[_arForTable objectAtIndex:indexPath.row];
// encoding fix
NSString *correctStringTitle = [NSString stringWithCString:[[item objectForKey:#"key"] cStringUsingEncoding:NSISOLatin1StringEncoding] encoding:NSUTF8StringEncoding];
cell.textLabel.text = [correctStringTitle capitalizedString];
NSNumber *num = [item objectForKey:#"id"];
cell.detailTextLabel.text = [num stringValue];
cell.detailTextLabel.hidden = YES;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
if([_selectedItems containsObject:indexPath]){
[_selectedItems removeObject:indexPath];
[self.categorySelected removeObject:[[self.arForTable objectAtIndex:indexPath.row] objectForKey:#"id"]];
string = [self.categorySelected componentsJoinedByString:#","];
[defaults setObject:string forKey:#"selectedCategoryTableString"];
NSLog(#"%# defaults from did select remove categorySelected",[defaults stringForKey:#"selectedCategoryTableString"]);
NSLog(#"%# STRING FROM contains / removeObj",string);
} else {
[_selectedItems addObject:indexPath];
[self.categorySelected addObject:[[self.arForTable objectAtIndex:indexPath.row] objectForKey:#"id"]];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
string = [self.categorySelected componentsJoinedByString:#","];
[defaults setObject:string forKey:#"selectedCategoryTableString"];
NSLog(#"%# providerSelected from did select add ",self.categorySelected);
NSLog(#"%# STRING FROM contains / addObj",string);
}
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
// [tableView reloadData];
}
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:NO];
[self.navigationController setNavigationBarHidden:YES animated:NO];
self.navigationController.toolbarHidden = YES;
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
// NSLog(#"ALL DEFAULTS %#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
NSLog(#"%# defaults from view appear categorySelected",[defaults stringForKey:#"selectedCategoryTableString"]);
string = [defaults stringForKey:#"selectedCategoryTableString"];
NSLog(#"%# STRING from will appear",string);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
also in the app delegate I have in the .h:
#property (strong, nonatomic) NSMutableArray *selectedCategories;
and in the .m:
`_selectedCategories = [NSMutableArray new];
in the didFinishLaunchingWithOptions: method
just to be clear:
when the view appears again (if I nslog the output) the mutablearray has been saved and is retrieved correctly. the mutable array only clears itself when a tablerow is touched again.
thanks if anyone can help with this. I've been stuck on it for some time...
edit
// DONT EVER EVER EVER EVER EVER EVER DO THIS!!!
// We don't use types as variable names, that is implicit...
// I get it, this is a string, BUT WHAT IS IT A STRING OF, the name
// 'string' does you, and anyone else, no good. Think about all your
// code like you are writing it for someone else, because when you come
// back to it in 6 months, you will be someone else, and you won't know
// what this means
NSString *string;
end edit
I would not be using NSUserDefaults this way. You have already parsed JSON into an archiveable object (NSMutableArray). In viewDidLoad, you should probably try doing something like:
-(void)viewDidLoad
{
// Load the array from a plist file
self.dataYouNeed = [NSMutableArray arrayWithContentsOfFile:#"someFileName.plist"];
// If we got back nil, that file didn't exist, so call 'reloadMain',
// do your parsing there THEN SAVE to a plist using:
//
// [myArray writeToFile:#"someFileName.plist"]
//
if(self.dataYouNeed == nil) [self reloadMain];
// Then do the exact same thing when you try to persist your selection...
// aka do not store a CSV string, just store an Array, and call writeToFile:
// when you want to save, and arrayWithContentsOfFile when you want to read
// it back in
}
On top of that, depending on where your data is coming from, I would move all of your data out of the JSON files and set it up in a plist, then you can ditch all of your parsing code.... :). Basically I am saying this is all a little too complicated for such a simple task, make your own life easier.
edit
You may have an issue with not using 'self.string', simply referring to 'string' is dangerous, you are creating a new reference everytime. This is most likely creating a memory leak. (ARC is not magic, it can not handle ALL memory management for you)
edit
Ok, so re-reading your code, I noticed a few things.
1. Why do you store your CSV string in the 'string' instance var?
This is somewhat redundant. At no point do you ever read from this variable without having set it in the few lines of code before. It should just be an NSString declared with in the scope of the method.
2. Are you expecting '_selectedItems' to have retained your reference to the 'selectedCategories' array on your AppDelegate?
You can not make this assumption, especially without having made a #property declaration. ARC does not know how to handle it and will probably be releasing the reference when you leave the view. The more likely possibility is that you are creating a memory leak every time you set that variable. You can also not guarantee that viewDidLoad will be called again to reset the reference. You should probably be setting this in viewWillAppear.
3. Which NSMutableArray are you experiencing a nil reference to?
If it is '_selectedItems', consider #2. If it is 'categorySelected', this is also probably being released when this view disappears. If this is really what you are trying to persist, then why are you not populating it from the viewDidAppear method. The only thing you do in viewDidAppear is set the 'string' variable (which is never actually read from, like #1 says). Did you mean to set 'categorySelected' here? I believe you meant to get your list from NSUserDefaults, then populate 'categorySelected' using that string's componentsSeparatedByString: method, which returns an array
Every time a user goes to some other view and comes back then
self.categorySelected = [[NSMutableArray alloc] init]; gets executed resulting it to an empty array.
First serialize the array when leaving the view:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:#[#"1",#"2",#"3"]];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"myarray"];
[[NSUserDefaults standardUserDefaults] synchronize];
Then deserialize it when you are back to that view:
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myarray"];
NSArray *myarray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(#"MYARRAY %#", myarray);
Note: if key is not found then init a new array.
Can you try to change 'retain' to 'strong' here?
#property (nonatomic, retain) NSMutableArray *categorySelected;
I think the issue is, you are setting the selectedItems array in viewDidLoad method. Probably the viewDidLoad is working once.
Just add the following line in your viewWillAppear method:
_selectedItems = [(AppDelegate *)[[UIApplication sharedApplication] delegate] selectedCategories];