I have a UITableView which I want to populate with details from an array of objects. The tableview shows the same item on every line (the correct number of lines though!) I know this must be an easy one - but I can't see where I've gone wrong:
Code snippet of view that initializes the table data:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"Show Tank List"])
{
NSURL *myUrl = [[NSURL alloc]initWithString:#"http://localhost/~stephen-hill9/index.php"];
NSData *data = [[NSData alloc] initWithContentsOfURL:myUrl];
NSError *error;
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
int i;
NSMutableArray *tanksList;
tank *thisTank = [[tank alloc] init];
tanksList = [[NSMutableArray alloc] init];
for (i=0; i<json.count; i++) {
NSDictionary *bodyDictionary = [json objectAtIndex:i];
thisTank.tankNumber = [bodyDictionary objectForKey:#"ID"];
thisTank.tankProduct = [bodyDictionary objectForKey:#"Product_Desc"];
thisTank.tankPumpableVolume = [bodyDictionary objectForKey:#"Pumpable"];
[tanksList addObject:thisTank];
}
[segue.destinationViewController setTanks:tanksList];
}
}
...and the code that loads the table in the next view...
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;//keep this section in case we do need to add sections in the future.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.tanks count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Tank List Table Cell";
UITableViewCell *cell = [self.tankTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero];
}
tank *thisTank = [self.tanks objectAtIndex:indexPath.row];
cell.textLabel.text = thisTank.tankNumber;
return cell;
}
Move this:
tank *thisTank = [[tank alloc] init];
Inside your for loop. You're updating the same object over and over again.
Also, you're initialising the cell wrong - use the designated initialiser, and pass the reuse identifier in, otherwise you will create new cells all the time:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
And you really should follow objective-c naming conventions. Classes begin with upper case letters, everything else begins with a lower case letter. It makes your code much easier to read, for other people anyway.
reload the table every time!!!
[self.tableView reloadData];
Related
After all progress i made with your answers, my issue changed. So i am changing my question with clearer way. I have an UITableView which is showing my retrieved data from Parse.com. So i made a NSMutableArray for adding objects to that array when they are retrieved. But my problem is even i add objects to NSMutableArray, my table does not show anything but default screen of UITableView. I thing the issue is UITableView is formed before my NSMutableArray got its objects. Here is my code:
Note: The PropertyClass is the class which has the properties of my objects.
At MyTableViewController.h
#interface MyTableViewController : UITableViewController <CLLocationManagerDelegate> {
PFObject *object;
}
#property (strong, nonatomic) IBOutlet UITableView *MyTableView;
#end
At UITableViewController.m
#interface MyTableViewController ()
#property(strong)NSMutableArray *myNSMutableArray;
#end
#implementation MyTableViewController
#synthesize myNSMutableArray,MyTableView;
-(void) retrievingDataFromParse{
PFQuery *query = [PFQuery queryWithClassName:#"MyObjectsClass"];
[query whereKey:#"ObjectsNumber" lessThanOrEqualTo:10];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
if (objects.count==0) {
NSString *objectError = #"There no object retrieved from Parse";
PropertiesClass *PC = [[PropertiesClass alloc]initWithPropert1:objectError Propert2:nil Propert3:nil Propert4:nil];
[myNSMutableArray addObject:PC];
}
for (int i = 0; i < objects.count; i++) {
object = [objects objectAtIndex:i];
NSString *Propert1 = [object objectForKey:#"Propert1"];
NSNumber *Propert2 = [object objectForKey:#"Propert2"];
NSNumber *Propert3 = [object objectForKey:#"Propert3"];
NSString *Propert4 = [object objectForKey:#"Propert4"];
PropertiesClass *PC = [[PropertiesClass alloc]initWithPropert1:Propert1 Propert2:Propert2 Propert3:Propert3 Propert4:Propert4];
[myNSMutableArray addObject:PC];
};
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.myNSMutableArray = [NSMutableArray array];
[self retrievingDataFromParse];
[MyTableView reloadData];
}
- (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 [myNSMutableArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
PropertiesClass *PC= [myNSMutableArray objectAtIndex:indexPath.row];
cell.textLabel.text=PC.Propert1;
return cell;
}
Looking at your code i see that you never create a UITableViewCell, you should change this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
PropertyClass *PC = [myMutableArray objectAtIndex:indexPath.row];
cell.textLabel.text = PC.x;
return cell;
}
with this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (nil == cell){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
PropertyClass *PC = [myMutableArray objectAtIndex:indexPath.row];
cell.textLabel.text = PC.x;
return cell;
}
the method dequeueReusableCellWithIdentifier:forIndexPath: return a UITableViewCell only if there are unused, but already allocated, cells in your table view. otherwise it returns nil.
Also when you update the mutable array containing all your data you should call [yourTableView reloadData] to force the table view to reload its content.
Your code is quite cryptic. Few suggestions here.
First, rename variables and methods with camelCaseNotation (camel case notation). For example, MyMutableArray should be myMutableArray. RetrievingDataFromParse should be retrievingDataFromParse (and so on). Start upper case letter are for classes.
Second, what does this code mean (I put comment on your code)?
for (int i = 0; i < objects.count; i++) {
// where do you have defined object?
object = [objects objectAtIndex:i];
NSString *x = [object objectForKey:#"x"];
NSNumber *y = [object objectForKey:#"y"];
NSNumber *z = [object objectForKey:#"z"];
NSString *t = [object objectForKey:#"t"];
// is Mekan a subclass of PropertiyClass or what else?
PropertiyClass *Properties = [[Mekan alloc]initWithx:x y:y z:z t:t]
// what's MekanKalibi? Maybe you need to add Properties
[MyMutableArray addObject:MekanKalibi];
}
Edit
If you don't use iOS6 - (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier you should alloc-init cells.
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if(!cell) {
// alloc-init a new cell here...
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
// or if you don't use ARC
// cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
PropertyClass *PC = [myMutableArray objectAtIndex:indexPath.row];
cell.textLabel.text = PC.x;
return cell;
Edit 2
I don't know how parse works but I suppose it manages async requests. So, at the end of your for loop, just call reload data in the table.
Parse states:
The InBackground methods are asynchronous, so any code after this will run immediately. Any code that depends on the query result should be moved inside the completion block above.
I had the same problem. When you reload the table, you need to move it so it is inside the block. Worked for me.
I'm not 100% sure how the asynchronous parts affect it so. I know that the start of my viewDidload and the end occured then this block, hence the problem.
People should probably up this as this solves the issue.
Cheers.
All you have to do is reload tableView in the block... this will show data.
for (int i = 0; i < objects.count; i++) {
object = [objects objectAtIndex:i];
NSString *Propert1 = [object objectForKey:#"Propert1"];
NSNumber *Propert2 = [object objectForKey:#"Propert2"];
NSNumber *Propert3 = [object objectForKey:#"Propert3"];
NSString *Propert4 = [object objectForKey:#"Propert4"];
PropertiesClass *PC = [[PropertiesClass alloc]initWithPropert1:Propert1 Propert2:Propert2 Propert3:Propert3 Propert4:Propert4];
[myNSMutableArray addObject:PC];
};
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
**[MyTableView reloadData];**
}];
I have a dictionary of arrays, and each of these arrays has a number of arrays itself. Its a multidimensional array in a dictionary.
When I click on a UItable row, I push the view to another class, however I want to push along with the view, the array whose object key is the same as the row name that was clicked.
My code is as follows
(The class that does the pushing)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic -- create and push a new view controller
if(dvController == nil)
dvController = [self.storyboard instantiateViewControllerWithIdentifier:#"FoodCategoryView"];
//NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//NSMutableDictionary *keysAndObjects = [defaults objectForKey:#"keysAndObjects"];
Food *foodObj;
/*for (id key in [keysAndObjects allKeys]) {
NSArray *foodArray = [keysAndObjects objectForKey:key];
foodObj = [foodArray objectAtIndex:indexPath.row];
}*/
/*for (id key in [keysAndObjects allKeys]) {
NSArray *foodArray = [keysAndObjects objectForKey:key];
for (Food *food in foodArray) {
// do stuff with Food object
//NSLog(#"%#", food.foodName);
}
}*/
NSMutableArray *objects2 = [[NSMutableArray alloc] init];
NSMutableArray *objects1 = [[NSMutableArray alloc] init];
objects1 = [objects objectAtIndex:indexPath.row];
objects2 = [objects1 objectAtIndex:indexPath.row];
foodObj = objects2;
//NSLog(#"Object %#", [objects objectAtIndex:indexPath.row]);
NSLog(#"foodObj %#", foodObj);
//Get the detail view data if it does not exists.
//We only load the data we initially want and keep on loading as we need.
//[foodObj hydrateDetailViewData];
dvController.foodObj = foodObj;
[self.navigationController pushViewController:dvController animated:YES];
}
(the class that the view is pushed to)
- (UITableViewCell *)tableView:(UITableView *)tblView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier];
}
/*for (Food *food in foodObj) {
// do stuff with Food object
NSLog(#"%#", food.foodName);
cell.text = food.foodName;
}*/
//Food *food = [foodObj objectAtIndex:indexPath.row];
//cell.text = [foodObj objectAtIndex:indexPath.row];
cell.text = foodObj.foodName;
NSLog(#"%#", foodObj.foodName);
return cell;
}
As you can see there are lots of comments as I have tried lots of different ways to do it myself.
So what I am trying to do is create a notepad style addition to my app.
All I want is for it to work exactly like apples existing notepad where you click the "add" button in the top right, then it creates a new note that you can write in and then when you click done it adds the note to a Cell in a UITableView.
I already have the UITableView and everything set up I just need to know how to run this action
-(IBAction)noteAdd:(id)sender{
}
And then when you click that button it does what I described above.
How would I go about doing this? I'm a little lost.
This Is How I am Adding the TableView to the scene, just By the way.
//tableview datasource delegate methods
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return cameraArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(cell == nil){
cell = [[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
NSEnumerator *enumerator = [cameraArray objectEnumerator];
id anObject;
NSString *cellName = nil;
while (anObject = [enumerator nextObject]) {
cellName = anObject;
}
//static NSString *cellName = [cameraArray.objectAtIndex];
cell.textLabel.text = [NSString stringWithFormat:cellName];
return cell;
}
In UITableView
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
So you'd do something like
-(IBAction) noteAdd:(id)sender
{
NSIndexPath *newCellPath = [NSIndexPath indexPathForRow:cameraArray.count
inSection:0];
// I'm assuming cameraArray is declared mutable.
[cameraArray addObject:#"New item"];
[self.tableView insertRowsAtIndexPaths:#[newCellPath]
withRowAnimation:UITableViewRowAnimationFade];
}
While I'm at it, a few comments on your code:
I'm pretty sure this code:
NSEnumerator *enumerator = [cameraArray objectEnumerator];
id anObject;
NSString *cellName = nil;
while (anObject = [enumerator nextObject]) {
cellName = anObject;
}
is a rather roundabout way of getting the last string in the array. You could do that easier with cameraArray.lastObject. But I don't think that's what you want either, I think you're looking for
// XCode >= 4.5:
cellName = cameraArray[indexPath.row];
// XCode < 4.5:
cellName = [cameraArray objectAtIndex:indexPath.row];
And the next line:
cell.textLabel.text = [NSString stringWithFormat:cellName];
Best case, this creates an extraneous string. If the cell name happens to have a % in it, you'll almost certainly either get an error or an EXC_BAD_ACCESS. To fix that error you could use
cell.textLabel.text = [NSString stringWithFormat:#"%#", cellName];
but there's really no reason to. Just assign the string directly:
cell.textLabel.text = cellName;
Or if you insist on a copy:
cell.textLabel.text = [NSString stringWithString:cellName];
// OR
cell.textLabel.text = [[cellName copy] autorelease];
// OR
This is my sample twitter program it works but there are several errors
if I scroll up or down top and bottom cellls disappears some times I get an error saying :
BAD ACCESS CODE this happens on this row
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
Please Advice
#import "TableViewViewController.h"
#interface TableViewViewController ()
#end
#implementation TableViewViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"tweets array count : %d", tweets.count);
return tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TweetCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSLog(#"ROW : %d", indexPath.row);
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *text = [tweet objectForKey:#"text"];
NSString *name = [[tweet objectForKey:#"user"] objectForKey:#"name"];
cell.textLabel.text = text;
cell.detailTextLabel.text = [NSString stringWithFormat:#"by %#", name];
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchTweets];
}
- (void)fetchTweets
{
NSString *twitterURL = [NSString stringWithFormat:#"https://api.twitter.com/1/statuses/public_timeline.json"];
NSURL *fullURL = [NSURL URLWithString:twitterURL];
NSError *error = nil;
NSData *dataURL = [NSData dataWithContentsOfURL:fullURL options:0 error:&error];
tweets = [NSJSONSerialization JSONObjectWithData:dataURL
options:kNilOptions
error:&error];
}
.....
#end
I see problems in this function:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"tweets array count : %d", tweets.count);
//return tweets.count;
return 11; //default returns 20 but only shows 10 indexpath.Row so 11
}
You probably shouldn't be just returning 11 -- what if tweets has a different length? The table view will try to fetch a cell at an index which doesn't exist in your array. Try returning tweets.count instead.
To elaborate a bit further: The purpose of the tableView:numberOfRowsInSection: method isn't to tell iOS how many rows there are on the screen, it's how many rows there are in the entire tableView. This includes cells which aren't shown on screen.
Found out the answer My app doesn't support ARC because I didn't check if we use a retain at the NSJSONSerialization the error is fixed.
tweets = [[NSJSONSerialization JSONObjectWithData:dataURL
options:kNilOptions
error:&error] retain];
Why is numberOfRowsInSection hardcoded to 11?
Also you should have [self.tableView reloadData] after the tweets array is set up in fetchTweets
Why don't you just uncomment return tweets.count; in your numberOfRowsInSection method?
I am working for the first time in objective c and have come across an issue that I have not seen an answer for.
I am loading a set of data from a JSON data set and using it to populate a UITableView within a UITableViewController.
First when the view is loaded (viewDidLoad) I populate the array with the JSON data from a URL.
Next the data loads as it should. numberOfRowsInSection shows that there are 30 results in the array which is correct.
However The Iphone loads the entire set three times into the tableview.
Here is some code from the controller for that view:(Twitter is being used as an example and is not the actual data set I use)
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
//results = [[NSMutableArray alloc] init];
self.navigationItem.title=#"public_timeline";
self.tableView.allowsSelection = NO;
NSString *url = [[NSString alloc] init];
url = [[NSString alloc] initWithFormat:#"http://twitter.com/statuses/%s.json",#"public_timeline"];
if ([results count] == 0) {
[self parseJSON:url];
}
}
Here is the parseJSON (actual parse is done with the Cocoa JSON framework
-(void) parseJSON:(NSString *)URL{
NSURL *JSONURL = [NSURL URLWithString:URL];
NSData *responseData = [[NSData alloc] initWithContentsOfURL:JSONURL];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
results = [[NSMutableArray alloc] initWithArray:[responseString JSONValue]];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [results count];
}
then the cell's output
- (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] autorelease];
// Set up the cell...
}
NSDictionary *cdict = [results objectAtIndex:indexPath.row];
cell.textLabel.text = [cdict valueForKey:#"text"];
return cell;
}
I am not sure if what I am doing is the best way to do this, so if someone could help me out that would be great.
Check the numberOfSectionsInTableView Method and change the return Value to 1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}