I've been trying to parse an array and split it into separate strings. I have managed to successfully get this working by individually adding a fixed objectAtIndex when I set the strings.
Example:
NSString *weightString = [[[results valueForKey:#"Endurance"] objectAtIndex:7]objectAtIndex:2];
However, when I try and set the strings in a tableView didSelectRowAtIndexPath using any objectAtIndex the string always returns null. I need to be able to click on the tableView row using objectAtIndex:indexPath.row. I can't work out where I am going wrong. Feel free to request more code or queries.
Query:
PFQuery *arrayQuery = [PFQuery queryWithClassName:#"Exercises"];
[arrayQuery selectKeys:#[#"Endurance"]];
[arrayQuery orderByAscending:#"ExerciseName"];
[arrayQuery findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
if (!error) { self.arrayResults = [results valueForKey:#"Endurance"];
cellForRowAtIndexPath:
- (TableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"list";
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *cellTitle;
cell.cellTitle.text = [[browseAllArray objectAtIndex: indexPath.row] objectForKey:#"ExerciseName"];
if (cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//Exercise Bar Label
cellTitle = [[UILabel alloc] initWithFrame:CGRectMake(80, 20, 220, 60)];
cellTitle.font = [UIFont fontWithName:#"ethnocentric" size:14];
cellTitle.textColor = [UIColor blackColor];
cellTitle.numberOfLines = 2;
cellTitle.textAlignment = NSTextAlignmentCenter;
cell.cellTitle.text = [[browseAllArray objectAtIndex: indexPath.row] objectForKey:#"ExerciseName"];
[self.tableView reloadData];
}
cellTitle.text = [[browseAllArray objectAtIndex:indexPath.row]objectForKey:#"ExerciseName"];
return cell;
I have managed to solve my problem by changing the string in the didSelectRowAtIndexPath method.
Instead of using:
NSString *weightString = [[[results valueForKey:#"Endurance"] objectAtIndex:indexPath.row]objectAtIndex:2];
I used:
NSString *weightString = [[arrayResults objectAtIndex:indexPath.row] objectAtIndex:2];
I decided to try and use the array instead of results string as it wouldn't let me pull the information from the string. I would recommend following #Larme 's steps above as they definitely sent me in the right direction.
If people are trying to follow my steps make sure you remove the valueForKey as this was throwing a fatal error.
Related
Sorry for posting this question again but I've looked into many answers and neither of them was helpfull to solve my issue.
So this my code :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"radioCell";
RadioTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
[self configureCommentCell:cell atIndexPath:indexPath];
return cell;
}
when I scroll down my cell get mixed up and some of data are repeated, so I've tried this :
static NSString *CellIdentifier = #"memberCell";
RadioCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
and this :
RadioTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
}
But it didn't fixed my issue and I get white empty cells ? please how to fix this issue ?
Update
- (void)configureCommentCell:(RadioTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSDictionary *object;
if ([_dataArray[indexPath.section] isKindOfClass:[NSArray class]])
object = [_dataArray[indexPath.section] objectAtIndex:indexPath.row];
else
object = [[_dataArray[indexPath.section] valueForKey:#"radioList"] objectAtIndex:indexPath.row];
if (object[#"jsonUrl"]) {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:object[#"jsonUrl"] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//NSDictionary *tempObject = (NSDictionary *) responseObject;
if (![[responseObject objectForKey:#"type"] isEqualToString:#"error"]) {
NSDictionary *tempObject = [responseObject[#"data"] objectAtIndex:0];
cell.playingNow.text = tempObject[#"song"];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
cell.name.text = [NSString stringWithFormat:#" %#", object[#"title"]];
if (object[#"logoUrl"])
[cell.logo setImageWithURL:[NSURL URLWithString:object[#"logoUrl"]]];
}
I see that your problem is that you are fetching the data of you cells inside configureCommentCell that's called inside cellForRowAtIndexPath. which is wrong, because it too late to fetch data inside cellForRowAtIndexPath, in this delegate method you should return the cell.
this line may be called before retrieving the data from server :
cell.name.text = [NSString stringWithFormat:#" %#", object[#"title"]];
Instead you should:
Fetch the data inside a separate method for example fetchData
when the data is downloaded inside the completion block of AFNetworking method, store the data inside an NSArray called for example myDataArray still inside the completion block call [self.tableView reloadData];
In viewDidLoad method just call your method fetchData
And your cellForRowAtIndexPath should looks like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// hey please give me the cell to display ... harry up please
// please harry up ! oh my god you are fetching data from server
// while I am asking for the cell !
// ok I don't care do what you want
// I will return an empty cell anyway
// and guess what I will not take in consideration
// the retried data because it's inside a block
// which is called asynchronously
static NSString *cellIdentifier = #"radioCell";
RadioTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier]; }
// now before return the cell you need to update the content of cell
// maybe you have an array of items and you should update the label
// for example here and then return the cell
cell.usernameLabel = self.myDataArray[indexPath.row]; // example
return cell;
}
Well the TableView is reusing the cells, and you add the image every time a cell is displaid. Thus when reusing the cell you add an other image, but there already is an image.
You will have to reuse the image view, and only add the image if you create the cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifer = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifer];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifer]autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(20,0,30,44)];
imageView.tag = 1001;
[cell addSubview:imageView];
[imageView release], imageView= nil;
}
TabBarTestAppDelegate *delegate = (TabBarTestAppDelegate *)[[UIApplication sharedApplication] delegate];
NSArray *local = delegate.myData;
// ok, it's horrible, don't look at it :-)
cell.textLabel.text = [NSString stringWithFormat:#"%#%#", #" " ,[local objectAtIndex:indexPath.row]];
//
NSString* name = nil;;
if (indexPath.row == 0) {
name = #"topicon";
}
else if (indexPath.row + 1 == [local count]) {
name = #"bottomicon";
}
else {
name = #"innericon";
}
UIImageView *imageView = (UIImageView *)[cell viewWithTag:1001];
imageView.image = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:name ofType:#"png"]];
return cell;
}
I have created a UITableView which contains cells that display Users. Each cell is added within this method -tableView:cellForRowAtIndexPath:. And each cell has content linked to the specific user, like an UIImageView and UILabel.
The UITableView works properly as long as there is no more than 9-10 cells displaying. But when the number of cells become higher, so the user has to scroll down to view them all, that's when the odd behavior begins. Content from the first, second, third and so on, is added to cell number eleven, twelve, thirteen and so on. And when the user then scroll up, the content that is supposed to be on number 11, 12, 13 is now in the first, second and third cell...
I hope someone understands my problem, and know what is wrong here..
Here is the code I user to add cells.. Ignore the parse stuff though, I dont think it is relevant
- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
if (tableview == commentViewTableView) {
//Ignore this
} else if (tableview == tableView) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 34, 34)];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.clipsToBounds = YES;
[cell addSubview:imageView];
UILabel *usernameLabel = [[UILabel alloc] initWithFrame:CGRectMake(44, 0, 160, 44)];
usernameLabel.textAlignment = NSTextAlignmentLeft;
usernameLabel.font = [UIFont systemFontOfSize:17];
usernameLabel.backgroundColor = [UIColor clearColor];
[cell addSubview:usernameLabel];
UIImageView *hitImageView = [[UIImageView alloc] initWithFrame:CGRectMake(245, 9.5, 25, 25)];
hitImageView.contentMode = UIViewContentModeScaleAspectFill;
hitImageView.clipsToBounds = YES;
hitImageView.image = [UIImage imageNamed:#"hit.png"];
[cell addSubview:hitImageView];
NSString *key = //Code to retrieve userKey
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:key];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
[[object objectForKey:#"image1"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
NSString *ageString = [[NSString alloc] initWithFormat:#"%li", (long)age];
imageView.image = [UIImage imageWithData:data];
usernameLabel.text = [NSString stringWithFormat:#"%#, %#", [object objectForKey:#"username"], ageString];
}
}];
}
}];
}
}
return cell;
}
I solved my problem by doing changing the cell identifier to be unique. I don't know if this actually is the way to do it, or if it is good practice, but when I did it solved my problem. So it would be good with some feedback to know if this will cause any other problems I'm might be missing?
NSString *identifier = [NSString stringWithFormat:#"Cell%li", indexPath.row];
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
//My code..
}
Change your code like this:
- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
} // CLOSED PARANTHESES HERE!!!!
if (tableview == commentViewTableView) {
//Ignore this
} else if (tableview == tableView) {
// ... rest of your code here
}
}
There are a couple problems with the code. One is that special care must be taken with asynch calls inside the cellForRowAtIndex: datasource method. Another is that the cells are reused, so adding subviews to them each time they come into view will pile subviews upon subview.
Lets start with the asynch operation. #nburk correctly points out the issue, but its an overstatement to say you "can't do it". You could preload everything, but then user must wait for the whole table to be ready before they can see any of it. A good strategy here is lazy load.
Lazy load depends on a place to cache the loaded result. So lets make your datasource array an array of mutable dictionaries that look like this:
#{#"user": aPFUser, #"image": aUIImage };
It makes sense to prefetch the users, otherwise, you don't even know how many you have, so, in viewWillAppear:
// setup your model as #property(strong,nonatomic) NSMutableArray *users;
PFQuery *query = [PFUser query];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// build the datasource
self.users = [NSMutableArray array];
for (PFUser *user in objects) {
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:
#{ #"user": user };
];
}
[self.tableView reloadData];
}
}];
Now, in cellForRowAtIndexPath you do this:
NSMutableDictionary *userDictionary = self.users[indexPath.row];
// in the lazy pattern, if the model is initialized, we're done
// start by assuming the best
imageView.image = userDictionary[#"image"];
// but the image might be nil (it will start out that way) so, load...
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:key];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
[[object objectForKey:#"image1"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:data];
// this is the important part: this code doesn't run when the rest
// of the method runs. It happens later, when the request completes
// so don't assume anything about the state of the table. Instead
// treat the table like you would in other view controller methods
userDictionary[#"image"] = image;
// don't fool around with cell (it might be reused). Instead
// just tell the table to reload this row
[tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}];
}
}];
The next time that row scrolls into view, the data from the asynch request will be cached in your user dictionary.
Problem two is simpler: the code builds subviews unconditionally, even if the (reused) cell already has that subview. The answer, again, is that laziness is your friend. Try to get the subview from the cell, and only build it if you must...
// change all of your subview-building code to do this:
UIImageView *imageView = (UIImageView *)[cell viewWithTag:32];
if (!imageView) {
imageView = [[UIImageView alloc] init....
// same code as you had here, adding...
imageView.tag = 32;
}
// and so on for the cell's other subviews. be sure to advance the tag (33, 34, etc)
In sum, the cellForRowAtIndexPath has a few sections.
dequeue the cell
lazy-build subviews as above
as above: access your model and optimistically init the subviews from the model
if part of the model is missing, do an asynch call, update the model,
and reload the cell when done
com documentation, and I am using the PFQueryForTable method with two different tableViewCells to sort my parse objects when they are put into the cells. I am doing this so when the object returns a blank string I can reduce its height to 0. Unfortunately when I do this it does not run like I want to and the blank cells are loaded into the first first cell, and not the one that I want to reduce the height to.
My cellForRowAtIndexPath looks like:
-(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object{
BOOL blank = [[[object objectForKey:#"post"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] isEqualToString:#""];
static NSString *CellIdentifier = #"Cell";
static NSString *Cell2Identifier = #"Cell2";
if (!blank) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
UILabel *label;
label = (UILabel *)[cell viewWithTag:1];
label.text = [object objectForKey:#"post"]; //[object objectForKey:#"post"];
label.textAlignment = NSTextAlignmentCenter;
NSLog(#"posted cell");
return cell;
}
else {
UITableViewCell *cell2 = [tableView dequeueReusableCellWithIdentifier:Cell2Identifier forIndexPath:indexPath];
if (cell2 == nil) {
cell2 = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Cell2Identifier];
}
cell2.textLabel.text = [object objectForKey:#"post"];
NSLog(#"posted cell2");
return cell2;
}
}
Can someone please point me in the right direction?
Thank you.
OK, from your comment you are currently creating a query like this...
- (PFQuery *)theQuery
{
PFQuery *posts = [PFUser query];
[posts whereKey:#"location" nearGeoPoint:self.myLocation withinKilometers:10.0];
[posts includeKey:self.textKey];
return posts;
}
So all you need to do is add another where condition to it...
[posts whereKey:#"theText" notEqualTo:#""];
This will stop blank text posts from being sent down.
I'm trying to insert data into the rows I've created, I will get all info in my Log but it only shows the last info in all of my rows. Could anyone suggest a way to avoid this error?
Please offer me some advice thanks!
You are never re-populating the cells, actually. You are creating the initial visible cells, and just reusing them with the same content.. please look below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath
{
static NSString *CellIdentifier = #"TestCell";
TestCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// HERE YOU ONLY WANT TO INSTANTIATE THE CELL
NSArray *topObjects = [[NSBundle mainBundle] loadNibNamed:#"TestCell" owner:nil options:nil];
for (id currentObject in topObjects)
{
if([currentObject isKindOfClass:[TestCell class]])
{
cell = (TestCell *) currentObject;
break;
}
}
}
// HERE YOU WOULD ACTUALLY POPULATE THE CELL WITH DATA
NSArray *array = [server get_texts:10 offset:0 sort_by:0 search_for:#""];
NSMutableString *s = [[NSMutableString alloc] init];
for (testMetaData *m in array){
[s appendFormat:#"%# %# \n", m.title,m.note];
cell.title.text = m.title;
NSLog(#" title %# ", m.title);
}
return cell;
}
Some info about UITableView:
So, a properly setup tableView only allocates and uses a limited number of UITableViewCells. After allocating, say 5 cells (this number is determined by "How many cells can you see at any given time?"), it will take an already created cell that has been scrolled out of the visible area, and gives it back to you in that method you are using, so you can re-populate it. So, cell variable will not be nil at that time, and your server code never gets called.
I think it has to do with your for loop.
NSMutableString *s = [[NSMutableString alloc] init];
for (testMetaData *m in array){
[s appendFormat:#"%# %# \n", m.title,m.note];
cell.title.text = m.title;
NSLog(#" title %# ", m.title);
}
Your cell.title.text = m.titlewill get the last m.title info at the end of the for loop.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath
{
//Load Cell for reuse
static NSString *CellIdentifier = #"TestCell";
TestCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell =[ [[NSBundle mainBundle] loadNibNamed:#"TestCell" owner:nil options:nil] lastObject];
}
//appending text and config cell
NSArray *array = [server get_texts:10 offset:0 sort_by:0 search_for:#""];
NSString *t = [array objectAtIndex:indexPath.row];
//Config cell - Not sure what you want. Maybe 10 different rows
cell.title.text = t;
return cell;
}
I have an array of EventObject(s). Each object has title and city and many others attributes. I want to show title of cell as event title, and subtitle of cell as event city. I tried doing that in the following, but I only get the last object displayed 4 times (that is the number of objects in the array).
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
// NSDate *object = _objects[indexPath.row];
// cell.textLabel.text = [object description];
EventObject *eventInstance = [[EventObject alloc] init];
int row = [indexPath row];
eventInstance = eventObjectsArray[row];
cell.textLabel.text = eventInstance.eventTitle;
NSLog(#"Event Title: %#", eventInstance.eventTitle);
cell.detailTextLabel.text = eventInstance.eventCity;
return cell;
}
What did I do wrong? I am learning objective-c so beginners errors surely. Thanks
EDIT: This is how I am inserting objects:
//get events from database
eventsDictionary = [eventInstance getEventsObjects];
//process each key in dictionary and assign to eventInstance
for(id key in eventsDictionary)
{
eventInstance.eventId = [NSString stringWithFormat:#"%#", key[#"id"]];
eventInstance.eventTitle = [NSString stringWithFormat:#"%#", key[#"title"]];
eventInstance.eventDescription = [NSString stringWithFormat:#"%#", key[#"description"]];
eventInstance.eventCity = [NSString stringWithFormat:#"%#", key[#"city"]];
[eventObjectsArray addObject:eventInstance];
}
Suggestions
[This can be issue]
While adding objects in your eventObjectsArray make sure to have new insatnce of EventObject everytime before you add object
EventObject *eventInstance = [[EventObject alloc] init];
eventInstance.eventTitle = #"your title";
eventInstance.eventCity = #"your city";
[eventObjectsArray addObject:eventInstance];
[This is suggestion as you should know this to improve your coding standard and memory management]
While retriving objects back from array you really don't need to have instance of custom object rather have refrence to it like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
//EventObject *eventInstance = [[EventObject alloc] init]; //you don't need this
int row = [indexPath row];
EventObject *eventInstance = eventObjectsArray[row]; // make reference like this
cell.textLabel.text = eventInstance.eventTitle;
NSLog(#"Event Title: %#", eventInstance.eventTitle);
cell.detailTextLabel.text = eventInstance.eventCity;
return cell;
}