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
Related
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.
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'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];
}];
}
i'm a new iOS developer; i have an application with parse and dynamic cell but when i run the app i find that the app is crashed due to the reason on the title my code as the following
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
myArray = [[NSMutableArray alloc] init];
//create new view if no view is available for recycling
// view = [[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 250.0f, 300.0f)] autorelease];
FXImageView *imageView = [[[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 250.0f, 350.0f)] autorelease];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;
// imageView.reflectionScale = 0.5f;
// imageView.reflectionAlpha = 0.25f;
// imageView.reflectionGap = 10.0f;
imageView.shadowOffset = CGSizeMake(0.0f, 2.0f);
imageView.shadowBlur = 5.0f;
imageView.cornerRadius = 10.0f;
view = imageView;
[ProgressHUD dismiss];
NSString *string1 = [[NSUserDefaults standardUserDefaults] stringForKey:#"Class"];
PFQuery *query = [PFQuery queryWithClassName:[NSString stringWithFormat:#"%#",string1]];
query.cachePolicy = kPFCachePolicyNetworkElseCache;
//show loader view
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
myArray = [[NSMutableArray alloc] initWithArray:objects];
PFObject *object = [myArray objectAtIndex:index];
[file getDataInBackgroundWithBlock:^(NSData *data1, NSError *error) {
if (!error) {
((UIImageView *)view).image = [UIImage imageWithData:data1];
//[HUD hideUIBlockingIndicator];
}
}];
}
}];
return view;
}
i use for first screen UICollectionView with dynamic data from parse as the following code
pragma collectionView delegate
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
CALayer *layer = cell.layer;
[layer setCornerRadius:8.0f];
[layer setMasksToBounds:YES];
// [layer setBorderWidth:1.0f];
// layer.backgroundColor = [UIColor whiteColor].CGColor;
//can you click
PFObject *imageObject = [myArray objectAtIndex:indexPath.item];
PFFile *imageFile = [imageObject objectForKey:#"image"];
NSString *name = [imageObject objectForKey:#"name"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
UIImageView *imageView = (UIImageView *)[cell viewWithTag:100];
imageView.image = [UIImage imageWithData:data];
UILabel *title2 = (UILabel *)[cell viewWithTag:200];
title2.text = [NSString stringWithFormat:#"%#",name];
title2.font = [UIFont fontWithName:#"GESSTextMedium-Medium" size:12];
}
}];
return cell;
}
i need some help to know where is the error;
every time i receive this error on console [__NSArrayM objectAtIndex:]: index 2 beyond bounds [0 .. 1]
There is not enough information. We even don't know the exact line for this crash.
What is the actual number of objects in myArray?
What you return on collectionView:numberOfItemsInSection:
and so on..
Imho, the easiest solution would be debugging this crash. And finding out why index is larger then objectAtIndex: expected. Try this:
1) Open "Breakpoint navigator"
2) tap '+' button and choose "Add Exception Breakpoint" option
Now app will use this breakpoint on any exception while debugging.
Salutations and welcomes, new developer. The first thing you want to find out is where that exception is happening. To do that, you're going to need to turn on the "exception breakpoint".
The exact way you do that varies by Xcode version (thanks Apple). Assuming that you are using Xcode 6, there's a video here that explains it.
I think it is quite likely that your carousel is miss-reporting the number of items it has. Or for some other reason you are trying to get a carousel item that does not exist… perhaps the carousel expects a nil when it queries with an index of a cell outside of the existing range, but you're assuming the index is valid and hitting an array bounds exception. At a wild guess, possibly the error is with the lines here:
…
myArray = [[NSMutableArray alloc] initWithArray:objects];
PFObject *object = [myArray objectAtIndex:index];
…
I would guess that section is not a valid index in to myArray.
(By the way – myArray is a mutable copy of the objects array. Why are you doing this? You don't make any changes to myArray, so it seems unnecessary.)
You probably have a method collectionView: numberOfItemsInSection: in your collection view delegate. Or if you don't, you probably need one. What's the code in that at the moment?
Your problem is in this line: PFObject *object = [myArray objectAtIndex:index];
Your index for this is 2 but you only have 2 objects in your array. Since the max index of your array is 1 (0..1), it crashes. I suggest seeing where that method is called and how you are getting an index that is out of range.
It is probably one of these two lines:
PFObject *object = [myArray objectAtIndex:index];
or
PFObject *imageObject = [myArray objectAtIndex:indexPath.item];
The problem is that index (or indexPath.item in the second case) contains the value 2, yet myArray is of size 2 (therefore the only valid indexes are 0 and 1).
finally i find the problem where; i must define the array for iCarousel numberOfItems so the code will be as the following
- (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
//return the total number of items in the carousel
return ([myArray count]);
return ([titleArray count]);
return ([priceArray count]);
return ([sapArray count]);
}
so i escape from the index problem
thanks for all sharing with me
Please check your numberOfRowsInSection method and array count you are using.
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;