I'm trying to retrieve the total number of comments from a PFQuery. For some reason, the log shows the array being returned but the label doesn't change with the number as required. Here's the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"FeedCell";
FeedCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[FeedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
PFObject *post = [postArray objectAtIndex:indexPath.row];
[cell.captionView setText:[post objectForKey:#"tag"]];
cell.captionView.editable = NO;
cell.captionView.text = [post objectForKey:#"description"];
PFFile *theImage = [post objectForKey:#"image"];
NSData *imageData = [theImage getData];
cell.photoImageView.image = [UIImage imageWithData:imageData];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.captionView.selectable = NO;
[cell.shareThis setTintColor:[UIColor clearColor]];
cell.comments.tag = indexPath.row;
cell.likeForYa.tag = indexPath.row;
[cell.likeLabel setText:#""];
PFQuery *commentsQuery = [PFQuery queryWithClassName:#"Comment"];
[commentsQuery whereKey:#"photo" equalTo:post.objectId];
NSLog(#"sement: %#", commentsQuery);
[commentsQuery countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
if (number == 1) {
cell.likeLabel.text = #"1 comment";
NSLog(#"comment: %d", number);}
else if (number > 0) {
[cell.likeLabel setText:[NSString stringWithFormat:#"%d comments", number]];
NSLog(#" plus: %d", number);
}
}];
return cell;
}
The portion of the code to be doing the query is
PFQuery *commentsQuery = [PFQuery queryWithClassName:#"Comment"];
[commentsQuery whereKey:#"photo" equalTo:post.objectId];
NSLog(#"sement: %#", commentsQuery);
[commentsQuery countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
if (number == 1) {
cell.likeLabel.text = #"1 comment";
NSLog(#"comment: %a", number);}
else if (number > 0) {
[cell.likeLabel setText:[NSString stringWithFormat:#"%d comments", number]];
}
}];
Could someone please help me out? Thank you!
The table view cell needs a fact (a count) that is received asynchronously. It's natural to attempt that asynch request in cellForRowAtIndexPath, but it isn't good practice: (a) that request to will be fired over and over when the user scrolls, and (b) the cell that needs the fact may be reused (may correspond to a different row) by the time the request completes. Here's a better pattern:
Isolate the network code, just to stay sane:
- (void)commentCountForPost:(PFObject *)post withCompletion:(void (^)(NSNumber *))completion {
PFQuery *commentsQuery = [PFQuery queryWithClassName:#"Comment"];
[commentsQuery whereKey:#"photo" equalTo:post];
NSLog(#"sement: %#", commentsQuery);
[commentsQuery findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error) {
completion(#(array.count)); // wrap as an NSNumber
}];
}
Cache the results, so that we request up to one time for each row:
// keys will be indexPaths, values will be comment counts
#property(nonatomic,strong) NSMutableDictionary *commentCounts;
// be sure to initialize early to
self.commentCounts = [#{} mutableCopy];
Now in cellForRowAtIndexPath, remember a couple important things: (a) check the cache for an already fetched value, (b) do not retain the cell in the completion block, it may refer to the wrong row by the time the block runs. Instead, reload the row, knowing that the cached value will be there:
// ...
PFObject *post = postArray[indexPath.row];
// ...
[cell.likeLabel setText:#""];
NSNumber *commentCount = self.commentCounts[indexPath];
if (commentCount) {
self.likeLabel.text = [NSString stringWithFormat:#"%# comments", commentCount];
} else {
[self commentCountForPost:post withCompletion:^(NSNumber *count) {
self.commentCounts[indexPath] = count; // cache the result
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
}
return cell;
Sometimes I add the cache logic to the network code. How to do that should be obvious, but I can demonstrate if you'd like.
EDIT Hopefully you can see from the logic of the solution that when the server data changes, the client's cache goes out of date, and should be discarded. When this view controller knows about the change, it can do this:
// imagine we know the comment count changed at a specific indexPath
[self.commentCounts removeObjectAtIndex:indexPath.row];
[self.tableView reloadRowsAtIndexPaths:#[indexPath]];
// or, imagine we know that the comment count changed someplace, or in more than one places. call this...
- (void)emptyCacheAndReloadData {
[self.commentCounts removeAllObjects];
[self.tableView reloadData];
}
But if another view controller makes the change, this vc needs to learn about it, and that's a different problem of a sort often asked about on SO. I'd encourage you to read the answer given here, which is correct and fairly comprehensive. If this is the first time you've tackled that topic, you may -- understandably -- want to first try a little shortcut. That would be this (matching your intuition about viewWillAppear):
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self emptyCacheAndReloadData];
}
EDIT 2 The lazy load and cache approach described here expends the effort to do the asynch work as each table cell needs display. Once the cache is initialized for a row, display of that row is fast, but the table will feel a little bumpy on the first scroll through.
We have to do the counting work someplace, and the best place is probably in the cloud, after saving a comment. There we could grab the post that the comment pertains to, count it's total comments, and save that sum on the post. With that you can skip my whole solution above, and just say something like...
self.likeLabel.text = [NSString stringWithFormat:#"%# comments", post[#"commentCount"]];
But this assumes you're maintaining a comment count property on Post using cloud code. Without cloud code, we need to move the initial work someplace else on the client. It must happen after the posts (your postArray) are loaded, but before the table view is reloaded. Find that place in your code and call a function like this...
- (void)postsLoaded {
// build an array of indexPaths in your table. this is just a row for each post
NSMutableArray *indexPaths = [#[] mutableCopy];
for (int i=0; i<self.postArray.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[indexPaths addObject:indexPath];
}
// now preload the counts
[self preloadCountsForIndexPaths:indexPaths completion:^(BOOL success) {
[self.tableView reloadData];
}];
}
// this is a recursive method. to count comments on array of posts
// count them on the first post, then count comments on the rest
- (void)preloadCountsForIndexPaths:(NSArray *)indexPaths completion:(void (^)(BOOL))completion {
if (indexPaths.count == 0) return completion(YES);
NSIndexPath *indexPath = indexPaths[0];
NSArray *remainingIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(1, indexPaths.count-1)];
PFObject *post = self.postArray[indexPath.row];
[self commentCountForPost:post withCompletion:^(NSNumber *count) {
self.commentCounts[indexPath] = count; // cache the result
[self preloadCountsForIndexPaths:remainingIndexPaths completion:completion];
}];
}
Related
I'm load an array of dictionary in a table view and after reload my table I called a method which change the data of dictionary from array and then refresh/reload particular index of tableview. But I'm not able to scroll my table until unless my complete data not updated again.
Code from where I call my method and reload table:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[Hud hideAnimated:true];
[self.myTbl reloadData];
dispatch_async(dispatch_get_main_queue(),^{
[self loadTutorials];
});
}
method in which I'm trying to update my table data.
#pragma mark:GetAllData
-(void)loadTutorials{
dispatch_async(dispatch_get_main_queue(),^{
int k = 0;
for (NSMutableString*linknew in linkArr) {
//here some calculations code for parsing then next
for (TFHppleElement *elements in contributorsNodes) {
// 5
for (TFHppleElement *child in elements.children) {
if ([child.tagName isEqualToString:#"img"]) {
// 7
#try {
NSString*url = [child.attributes objectForKey:#"src"];
NSMutableDictionary*dict = [[feeds objectAtIndex:k] mutableCopy];
[dict setObject:url forKey:#"image"];
[feeds removeObjectAtIndex:k];
[feeds insertObject:dict atIndex:k];
NSIndexPath*index = [NSIndexPath indexPathForRow:k inSection:0];
[self.myTbl reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationNone];
}
#catch (NSException *e) {}
}
}
}k++;
}
});
}
cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
UILabel*lbl = (UILabel*)[cell viewWithTag:101];
UIImageView*imgView = (UIImageView*)[cell viewWithTag:103];
[imgView sd_setImageWithURL:[NSURL URLWithString:[[feeds objectAtIndex:indexPath.row] objectForKey:#"image"]] placeholderImage:[UIImage imageNamed:#"img.jpg"]];
lbl.text = [NSString stringWithFormat:#"%#",[[feeds objectAtIndex:indexPath.row] objectForKey: #"title"]];
return cell;
}
Have many things in your question make me confused.
With parserDidEndDocument method, I don't know why you need to call [self.myTbl reloadData] although after that you update self.myTbl inside loadTutorials method.
Why do you need to call 2 dispatch_async here, both outside and inside loadTutorials method? I think it's not necessary. Just call 1 time.
To replace an object in an array, you don't need to remove and insert object. Just replace by using feeds[k] = dict. Make sure feeds.count > k before using.
As i guess, you use try catch because you got crash when updating self.myTbl inside loadTutorials method. In my opinion, you should use beginUpdates and endUpdates instead of try catch.
I think the problem is because you used try catch to handle crash.
Try to rewrite you code with background queue as #Losiowaty suggested, we will have
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[Hud hideAnimated:true];
[self.myTbl reloadData];
[self loadTutorials];
}
#pragma mark:GetAllData
-(void)loadTutorials{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
int k = 0;
for (NSMutableString*linknew in linkArr) {
//here some calculations code for parsing then next
for (TFHppleElement *elements in contributorsNodes) {
// 5
for (TFHppleElement *child in elements.children) {
if ([child.tagName isEqualToString:#"img"]) {
NSString*url = [child.attributes objectForKey:#"src"];
// I assume in some case, feeds[k] doesn't exist. We need add an
// NSMutableDictionary to this index before using to avoid crash.
// If you make sure feeds[k] always exist you can ignore it
if (feeds.count == k) {
[feeds addObject:[[NSMutableDictionary alloc] init]];
}
[feeds[k] setObject:url forKey:#"image"];
NSIndexPath*index = [NSIndexPath indexPathForRow:k inSection:0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.myTbl beginUpdates];
[self.myTbl reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationNone];
[self.myTbl endUpdates];
});
}
}
}k++;
}
});
}
Let try the code first. If you still have any problem, let me know. I will help you.
I am working on a app that you can change when a item on the menu is in stock or out of stock.
I have it now so it changes the UISwitch to on or off when it loads the screen. I need each switch to change a NSString in parse that makes it one or zero.One meaning that it is on zero meaning its off.
I am fairly new to objective c and parse so if any one could help me get a start on this problem that would be great!
You might use something like that:
PFQuery *query = [PFQuery queryWithClassName:#"YourClass"];
[query whereKey:#"user" equalTo:[PFUser currentUser]];
[query getFirstObjectInBackgroundWithBlock:^(PFObject * yourClass, NSError *error) {
if (!error) {
// Found yourClass object
[yourClass setObject:isInStock forKey:#"isInStock"];
// Save
[yourClass saveInBackground];
} else {
// Did not find any yourClass object for the current user
NSLog(#"Error: %#", error);
}
}];
NSArray *listObjects = .... (loading from Server) // List of PFObject
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
PFObject *object = [listObjects objectAtIndex:indexPath.row];
YourCell *cell = .....
if ([[object valueForKey:#"sandwichesOutofstock"] intValue] == 1)
cell.switch.on = true;
else
cell.switch.on = false;
cell.switch.tag = 500 + index.row;
[cell.switch addTarget:self action:#selector(switchTouch:) forControlEvents:UIControlEventTouchUpInside]
.........
}
(IBAction)switchTouch:(UISwitch *)switch{
long index = switch.tag - 500;
PFObject *object = [listObjects objectAtIndex:index];
if(switch.on)
[object setValue:#"1" ForKey:#"sandwichesOutofstock"];
else{
[object setValue:#"0" ForKey:#"sandwichesOutofstock"];
}
[object saveInBackground];
[self.tableView reloadRowsAtIndexPaths:[NSIndexPath indexPathForRow:index inSection:0] withRowAnimation:UITableViewRowAnimationNone];
}
You could assign a reference of the PFObject to the cell. Then when the switch changes just get the cell's object and make the change.
I'm using UITableViewController for displaying data from Parse. It runs perfectly on my Xcode Simulator as i think there's no latency in network. But whenever i'm uploading the code to AppStore for Testing. The very first time i run the app it has to load a couple of restaurant's from Parse and display in UITableViewController. Upon clicking a row the first rows data is being loaded into the 3rd row and 4th row data loading in 6th row data irregularly. Why is the data being loaded very unevenly ? Here's my
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellIdentifier = #"restaurantIdentifier";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
PFObject *tempObject = [self.objectArray objectAtIndex:indexPath.row];
PFFile *imageFile = [tempObject objectForKey:#"RestaurantIcon"];
PFImageView *imageView = [[PFImageView alloc] init];
imageView.file = imageFile;
[imageView loadInBackground:^(UIImage *img,NSError *error){
if(!error){
cell.imageCell.image = imageView.image;
}
}];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 4;
cell.imageView.frame = self.view.bounds;
cell.cellLabel.text = [tempObject objectForKey:#"RestaurantName"];
[self.hotelNamesArray addObject:[tempObject objectForKey:#"RestaurantName"]];
cell.cellLabel.lineBreakMode = NSLineBreakByWordWrapping;
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
_restaurantName = [self.hotelNamesArray objectAtIndex:indexPath.row];
self.restaurantMenuNameArray = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:[self.hotelNamesArray objectAtIndex:indexPath.row]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *obj in objects) {
if (![_restaurantMenuNameArray containsObject:[obj objectForKey:#"RestaurantMenuName"]]) {
NSLog(#"restaurantmenunames are %#",[obj objectForKey:#"RestaurantMenuName"]);
if ([obj objectForKey:#"RestaurantMenuName"] ==nil) {
[self performSegueWithIdentifier:#"restaurantDetail" sender:self];
return;
}else {
[_restaurantMenuNameArray addObject: [obj objectForKey:#"RestaurantMenuName"]];
}
}
}
}else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
[self.tableView reloadData];
NSLog(#"restaurantMenuNames is %#",_restaurantMenuNameArray);
[self performSegueWithIdentifier:#"restaurantDetail" sender:self];
}];
}
Thanks in advance.
If you mean the images get in the wrong cell, you have to consider that cells are recycled when you scroll, and that if the image loading takes a bit too long, you may get the result after the cell has been reused.
You need to check that the cell is still for the item/row you want (you could store the row in the cell's tag and check it before setting the image in the completion handler, for instance).
If it's other data that is mixed up, then you'll need to show us the code that loads that data.
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
I'm pretty new at this and having a go using Parse.com as the backend server. I'm building a database of vegetables, and want to perform a search on the list pulled from parse.com. I have it all working, except for one annoying thing...
Now, I'm using storyboards and have created a custom cell which includes a PFImage thumbnail view, a label showing the vegetable, and then another label showing the season for the vegetable.
When the viewcontroller is called, the list populates perfectly and lists the vegetables in alphabetical order. Then I drag the window down to reveal the search bar. I begin typing in a vegetable name, and as I do so the original table data rows begin disappearing (as they should), but the problem is the original table data sticks around. So, for instance, I'll type "carrot", and all the rows disappear except the top row which still holds a thumbnail of an artichoke (and the label "Artichoke" as well). But overlayed on that row is also the word "Carrots", which is another vegetable in the list. If I tap on it, it properly seques to my detail view controller showing carrots. So everything is working properly, but I can't figure out how to make it so the search results aren't being written over the top of the original data.
Here's the code portions:
- (void)viewDidLoad
{
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.backgroundView=[[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"bg.jpg"]];
[super viewDidLoad];
//add the search bar
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
self.tableView.tableHeaderView = self.searchBar;
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
self.searchController.delegate = self;
CGPoint offset = CGPointMake(0, self.searchBar.frame.size.height);
self.tableView.contentOffset = offset;
self.searchResults = [NSMutableArray array];
//done adding search bar
}
- (PFQuery *)queryForTable
{
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
// If no objects are loaded in memory, we look to the cache first to fill the table
// and then subsequently do a query against the network.
/* if ([self.objects count] == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}*/
[query orderByAscending:#"vegetable"];
return query;
}
// Override to customize the look of a cell representing an object. The default is to display
// a UITableViewCellStyleDefault style cell with the label being the first key in the object.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
static NSString *simpleTableIdentifier = #"VegetableCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
// more search stuff
if (tableView == self.tableView) {
cell.backgroundColor = cell.backgroundColor;
}
else if(tableView == self.searchDisplayController.searchResultsTableView) {
PFObject *searchedVeggie = [self.searchResults objectAtIndex:indexPath.row];
cell.textLabel.text = [searchedVeggie objectForKey:#"vegetable"];
cell.backgroundColor = [UIColor whiteColor];
tableView.rowHeight = self.tableView.rowHeight;
}
PFFile *thumbnail = [object objectForKey:#"vegetableImageFile"];
PFImageView *thumbnailImageView = (PFImageView*)[cell viewWithTag:100];
thumbnailImageView.image = [UIImage imageNamed:#"placeholder.jpg"];
thumbnailImageView.file = thumbnail;
[thumbnailImageView loadInBackground];
UILabel *vegetableName = (UILabel*) [cell viewWithTag:101];
vegetableName.text = [object objectForKey:#"vegetable"];
UILabel *vegetableSeason = (UILabel*) [cell viewWithTag:102];
vegetableSeason.text = [object objectForKey:#"vegetableSeason"];
return cell;
}
Lower in the code is my prepareForSeque code and other search methods. I know it's a little ugly with repeated code, but I've been trying all sorts of things to fix my issue and wasn't going to get around to cleaning things up until I figured out the issue. Also, I created a new column on parse.com's data browser called lowerCaseVegetable since the search is case sensitive. So the search is actually performed on that column, but is displayed using the normal "vegetable" column, which has the vegetable name capitalized.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showVegetableDetail"]) {
if (self.searchDisplayController.active) {
NSLog(#"Search Display Controller");
VeggieDetailViewController *destViewController = segue.destinationViewController;
PFObject *object = [self.searchResults objectAtIndex: self.searchDisplayController.searchResultsTableView.indexPathForSelectedRow.row];
Vegetables *vegetables = [[Vegetables alloc] init];
vegetables.vegetable = [object objectForKey:#"vegetable"];
vegetables.vegetableInfo = [object objectForKey:#"vegetableInfo"];
vegetables.vegetableImageFile = [object objectForKey:#"vegetableImageFile"];
vegetables.vegetableSeason = [object objectForKey:#"vegetableSeason"];
destViewController.vegetables = vegetables;
} else {
NSLog(#"Default Display Controller");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
VeggieDetailViewController *destViewController = segue.destinationViewController;
PFObject *object = [self.objects objectAtIndex:indexPath.row];
Vegetables *vegetables = [[Vegetables alloc] init];
vegetables.vegetable = [object objectForKey:#"vegetable"];
vegetables.vegetableInfo = [object objectForKey:#"vegetableInfo"];
vegetables.vegetableImageFile = [object objectForKey:#"vegetableImageFile"];
vegetables.vegetableSeason = [object objectForKey:#"vegetableSeason"];
destViewController.vegetables = vegetables;
}
}
}
// other search stuff
-(void)filterResults:(NSString *)searchTerm {
[self.searchResults removeAllObjects];
PFQuery *query = [PFQuery queryWithClassName: #"Vegetables"];
[query whereKeyExists:#"lowerCaseVegetable"];
[query whereKey:#"lowerCaseVegetable" containsString:searchTerm];
NSArray *results = [query findObjects];
[self.searchResults addObjectsFromArray:results];
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self filterResults:searchString];
return YES;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.tableView) {
return self.objects.count;
} else {
return self.searchResults.count;
}
}
-(void)callbackLoadObjectsFromParse:(NSArray *)result error:(NSError *)error {
if (!error) {
[self.searchResults removeAllObjects];
[self.searchResults addObjectsFromArray:result];
[self.searchDisplayController.searchResultsTableView reloadData];
} else {
// NSLog(#”Error: %# %#”, [error userInfo]);
}
}
I have a feeling I'm just making a stupid newbie mistake here, but I've only been at this since May, and specifically fighting this issue the last two weeks. I figured it was about time to ask for help.
I just implemented search in my parse app and I think I see the problem:
In your filterResults method try using findObjectsInBackgroundWithBlock instead of findObjects like this:
[query findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error){
[self.searchResults addObjectsFromArray:array];
[self.searchDisplayController.searchResultsTableView reloadData];
}];
prepareForSegue:
Vegetables *vegetables = [[Vegetables alloc] init];
vegetables.vegetable = [self.selectedObject objectForKey:#"vegetable"];
vegetables.vegetableInfo = [self.selectedObject objectForKey:#"vegetableInfo"];
vegetables.vegetableImageFile = [self.selectedObject objectForKey:#"vegetableImageFile"];
vegetables.vegetableSeason = [self.selectedObject objectForKey:#"vegetableSeason"];
destViewController.vegetables = vegetables;