I have a probem when trying to reload a UICollectionview in iOS.
I am using it to display levels in a game.
The collectionview consists of 10 cells. The content of the cells depends if a level is unlocked. If the level is unlocked the cell displays the level (a custom UIView), else it displays an image.
I had to create individual cell identifiers for this to work, and everything displays perfectly on load.
My problem is when a user is playing an unlocked level and then unlocks the next level. When the user navigates back from the game view to the level selection view, the cells are not reloaded correctly (just shows up empty where the custom views should be, the images display correctly).
I have tried to unload the array with levels in viewWillAppear and then calling [collectionview reloadData];, then loading the levels and reloading the collectionview again, but that does not help.
How can I empty the entire collectionview and recreate the cell identifiers when the view is displayed?
Thanks
-EDIT! UPDATED WITH CODE -
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
levelsArray = nil;
puzzlesArray = nil;
levelsArray = [[NSMutableArray alloc]init];
puzzlesArray = [[NSMutableArray alloc]init];
[collectionView reloadData];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:#"puzzles"];
puzzlesArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if ([puzzlesArray count] == 0) {
[self.navigationController popViewControllerAnimated:YES];
}
else {
NSLog(#"%i puzzles loaded", [puzzlesArray count]);
//Get alle puzzles for the current category
for (Puzzle *puzzle in puzzlesArray) {
if ([[[NSUserDefaults standardUserDefaults]objectForKey:#"Category"] isEqualToString:[puzzle categoryName]]) {
[levelsArray addObject:puzzle];
}
}
}
NSLog(#"View will appear");
[collectionView reloadData];
}
And in the cell for item at index path
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath; {
BOOL isUnlocked = [self isPuzzleUnlocked:[indexPath row]];
if ([[NSUserDefaults standardUserDefaults]boolForKey:#"u"] == YES) {
isUnlocked = YES;
}
[self.collectionView registerNib:[UINib nibWithNibName:#"CVCell" bundle:nil] forCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d", kCellReuseIdentifier, indexPath.row]];
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d", kCellReuseIdentifier, indexPath.row] forIndexPath:indexPath];
[cell setPuzzleInfo:[levelsArray objectAtIndex:[indexPath row]] unlocked:isUnlocked];
return cell;
}
In line with Rob's suggestion.
-(void)viewWillAppear {
[super viewWillAppear];
[self.collectionView reloadData];
}
In cellForItemAtIndexPath you should simply check if the item is unlocked or not and display the proper image or custom cell.
That's all. It is definitely not necessary to recreate the collection view.
You might want to honour the MVC (model-view-controller) design pattern by giving both your game controller and the level controller access to the data that models the levels. When what is happening in the game unlocks a new level, it should change this data. The collection view should reload itself based on this data just before it appears.
EDIT:
If you get the same cells/items as before even though you reloadData, this means that your configuration is not set up correctly. The key is to set both states of unlocked explicitly - reloadData will then update the item if it has changed.
EDIT2:
Both your itemForRowAtIndexPath methods are messed up. The call to registerNib does not belong there at all! It is supposed to be called during the initialization process. If you use storyboard, it is not necessary at all.
Here is a simple framework that you should use:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv
cellForItemAtIndexPath:(NSIndexPath *)indexPath; {
PuzzleInfo *puzzleObject = [dataArray objectAtIndex:indexPath.row];
// you can use your more complicated data structure here, but
// have one object, dictionary, array item, etc. that has the info you need
NSString *identifier = puzzleObject.unlocked ? kUnlockedCell : kLockedCell;
CVCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier
forIndexPath:indexPath];
if (puzzleObject.unlocked) {
// configure the unlocked cell
}
else {
// configure the locked cell
}
return cell;
}
I fixed it!
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath; {
NSLog(#"CALLED");
BOOL isUnlocked = [self isPuzzleUnlocked:[indexPath row]];
if ([[NSUserDefaults standardUserDefaults]boolForKey:#"u"] == YES) {
isUnlocked = YES;
}
int rand = arc4random() % 99001;
[self.collectionView registerNib:[UINib nibWithNibName:#"CVCell" bundle:nil] forCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d%i", kCellReuseIdentifier, indexPath.row, rand]];
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d%i", kCellReuseIdentifier, indexPath.row, rand] forIndexPath:indexPath];
[cell setPuzzleInfo:[levelsArray objectAtIndex:[indexPath row]] unlocked:isUnlocked];
return cell;
}
Not a very memory effecient solution (or elegant), but it works. Adding a random number to each cell forces it to be recreated each time. As long as I only have 10 cells in a collectionview I think it will be ok..
Thanks for all your help :]
Related
I'm new in CoreData and using MagicalRecord to rule with it. My problem is that I have the UITableView with an NSArray as dataSource populated with objects which fetched from CoreData db, and everything seems fine until I scroll the table for some times.
Here is my code:
Method for fetching data (MyDatabase.m):
+(NSArray *)getEntities{
...
return [MyEntity MR_findAllSortedBy:#"name" ascending:YES withPredicate:predicate];
}
Here is how I fetch and set data to UITableView in my ViewController:
- (void)viewDidLoad {
[super viewDidLoad];
myEntitiesArray = [MyDatabase getEntities];
if(myEntitiesArray.count != 0)
[myTableView setTableData:myEntitiesArray];
}
Here is setTableData method implementation in MyTableView.m:
- (void)setTableData:(NSArray *)array {
if (array && [array count] > 0) {
_tableData = array;
[self reloadData];
}
}
And here is how I set up my cells in MyTableView.m:
- (void)tableView:(UITableView *)tableView willDisplayCell:(SSCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
}
I tried to put an NSLog(#"name is %#",[(MyEntity *)_tableData[indexPath.row] name]) into willDisplayCell and found that when cells become empty, NSLog prints out the messages "name is (null)". I know this question is possibly solved by many people and many times before I faced this problem. Hope someone will help me to solve it too :)
UPDATE: cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"ssCell";
SSCell *cell = (SSCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if( !cell ) {
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell setSelectedBackgroundView:selectedBackgroundView];
}
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
return cell;
}
I also call this method inside MyTableView.m init method:
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:#"ssCell"];
You have to use cellForRowAtIndexPath. In this method the cells are allocated.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/*
* This is an important bit, it asks the table view if it has any available cells
* already created which it is not using (if they are offscreen), so that it can
* reuse them (saving the time of alloc/init/load from xib a new cell ).
* The identifier is there to differentiate between different types of cells
* (you can display different types of cells in the same table view)
*/
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
/*
* If the cell is nil it means no cell was available for reuse and that we should
* create a new one.
*/
if (cell == nil) {
/*
* Actually create a new cell (with an identifier so that it can be dequeued).
*/
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MyIdentifier"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
/*
* Now that we have a cell we can configure it to display the data corresponding to
* this row/section
*/
cell.nameLabel.text = [(MyEntity *)_tableData[indexPath.row] name];
return cell;
}
You should be initializing the cell by calling init. Instead you are doing the following:
if( !cell ) {
[self registerNib:[UINib nibWithNibName:#"SSCell" bundle:nil] forCellReuseIdentifier:cellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell setSelectedBackgroundView:selectedBackgroundView];
The second call attempts to again reuse an existing cell when there isn't one available. That would probably return nil again.
be very careful of the "feature" of objective C, where calling a method of a nil object does nothing. Instead of crashing with null.pointer.exception like Java, it probably floats over [cell setSelectedBackgroundView:selectedBackgroundView] and a whole bunch of other lines without a problem.
I have a UITableView and UICollectionView loading the same content, but, even though their protocol methods are defined almost identically, the collection view is giving me an empty view, whereas, the table view displays content. So that you understand the context of this question, I will cover what goes on before loading content on both views:
I get back an image from -imageWithData and store it in a dictionary
This dictionary is then accessed by the TableViewController class as well as the CollectionViewController class and implemented as such:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Configure the cell...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"User ID" forIndexPath:indexPath];
//setting UI
NSString *milesFrom = self.streamItems[indexPath.row][#"MilesAway"];
cell.textLabel.text = [NSString stringWithFormat:#"%#, Distance Away: %#", self.streamItems[indexPath.row][#"UserName"], milesFrom];
UIImage *photo = [[UIImage alloc]init];
photo = self.streamItems[indexPath.row][#"ThumbnailPhoto"];
cell.imageView.image = photo;
return cell;
}
This part works just as I need it to, but the collection view does not:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"MyCell";
CellViewController *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImage *imageReturned = [[UIImage alloc]initWithCIImage: self.streamItems[indexPath.row][#"ThumbnailPhoto"]];
cell.imageOfCell.image = imageReturned;
[cell.imageOfCell sizeToFit];
return cell;
}
This CellViewController Class has the IBOutlet 'imageOfCell' property. This property references an Image View object that I dragged on top of the cell ("MyCell") that is inside of my collection view. The collection view, as I said, gives me a blank view with no content. In debugging, I saw that both the 'photo' instance and 'ImageReturned' instance had the same exact values. For this reason, I have no idea why the collection view does not load the images but the table view does. In case it has anything to do with the way I load the collection view, I have included my -viewDidLoad method definition:
#implementation StreamCollectionViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass: [CellViewController class]forCellWithReuseIdentifier:#"MyCell"];
UICollectionViewFlowLayout *flow = [[UICollectionViewFlowLayout alloc]init];
[flow setScrollDirection:UICollectionViewScrollDirectionVertical];
[self.collectionView setCollectionViewLayout:flow];
//create object to deal with network requests
StreamController *networkRequester = [[StreamController alloc]init];
[networkRequester getFeedWithCompletion:^(NSMutableArray *items, NSError *error) {
if (!error) {
self.streamItems = items;
[self.collectionView reloadData];
}
else{
NSLog(#"Error getting streamItems: %#", error);
}
}];
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = NO;
// Register cell classes
// [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
// Do any additional setup after loading the view.
}
I am fairly new to using the collection view so I wouldn't be surprised if it's something relatively small that I am missing. Thanks in advance!
In delegate of uicollectionView cellforrowatindexpath
After
CellViewController *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
Add this
if(cell == nil)
cell = [[CellViewController alloc]init];
I am trying to make some photoPicker with CollectionView.
Have
allowsMultipleSelection = YES
Using following method
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array];
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]];
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
While I am selecting cells, it's always adding to MutableArray only one object according it's indexPath. What could be an issue?
Why don't u keep the selectedPictures as a member variable
in your code
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
try this
put his in viewDidLoad
- (void)viewDidLoad
{
selectedPictures = [[NSMutableArray alloc]init]; //initilise hear
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image to already initialised array
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
Hope this helps u .. :)
it may be caused by not calling super. While the documentation for UICollectionReusableView fails to mention this, the documentation for UITableViewCell, which has the same method, does.
- (void)prepareForReuse
{
[super prepareForReuse]
// Your code here.
}
Old Answer:
This may be a bug with the UICollectionView.
What's happening is cells that were previously selected are being reused and maintain the selected state. The collection view isn't setting selected to "NO".
The solution is to reset the the selected state in prepareForReuse of the cell:
- (void)prepareForReuse
{
self.selected = NO;
}
If the reused cell is selected, the collection view will set selected to "YES" after prepareForReuse is called.
This is something the UICollectionView should be doing on it's own. Thankfully the solution is simple. Unfortunately I spent a ton of time working around this bug by tracking my own select state. I didn't realize why it was happening until I was working on another project with smaller cells.
Also Try this
I'm not seeing why this would take place. I do not believe the issue is the use of row vs item, though you really should use item. I can imagine, though, if your collection view has more than one section, that only looking at row/item but ignoring section would be a problem (i.e. it would select the same item number in every section).
To cut the Gordian knot, I'd suggest saving the NSIndexPath of the selected item, and then using that for the basis of comparison. That also makes it easy to render an optimization in didSelectItemAtIndexPath. Anyway, first define your property:
#property (nonatomic, strong) NSIndexPath *selectedItemIndexPath;
And then implement cellForItemAtIndexPath and didSelectItemAtIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.imageView.image = ...
if (self.selectedItemIndexPath != nil && [indexPath compare:self.selectedItemIndexPath] == NSOrderedSame) {
cell.imageView.layer.borderColor = [[UIColor redColor] CGColor];
cell.imageView.layer.borderWidth = 4.0;
} else {
cell.imageView.layer.borderColor = nil;
cell.imageView.layer.borderWidth = 0.0;
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// always reload the selected cell, so we will add the border to that cell
NSMutableArray *indexPaths = [NSMutableArray arrayWithObject:indexPath];
if (self.selectedItemIndexPath)
{
// if we had a previously selected cell
if ([indexPath compare:self.selectedItemIndexPath] == NSOrderedSame)
{
// if it's the same as the one we just tapped on, then we're unselecting it
self.selectedItemIndexPath = nil;
}
else
{
// if it's different, then add that old one to our list of cells to reload, and
// save the currently selected indexPath
[indexPaths addObject:self.selectedItemIndexPath];
self.selectedItemIndexPath = indexPath;
}
}
else
{
// else, we didn't have previously selected cell, so we only need to save this indexPath for future reference
self.selectedItemIndexPath = indexPath;
}
// and now only reload only the cells that need updating
[collectionView reloadItemsAtIndexPaths:indexPaths];
}
Check also this
Your observation is correct. This behavior is happening due to the reuse of cells. But you dont have to do any thing with the prepareForReuse. Instead do your check in cellForItem and set the properties accordingly. Some thing like..
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cvCell" forIndexPath:indexPath];
if (cell.selected) {
cell.backgroundColor = [UIColor blueColor]; // highlight selection
}
else
{
cell.backgroundColor = [UIColor redColor]; // Default color
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor blueColor]; // highlight selection
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor redColor]; // Default color
}
I solved my issue;
The problem was very simple, I should have initialise MutableArray not in the Method didSelectItemAtIndexPath, but in the ViewDidLoad. Now it adding pictures one by one
Goal:
Set up two collectionViews with different styles. One grid style and other single file style. These collectionViews will be toggleable giving users shopping within the ability to view the items for sale in which ever style (grid/single file) they prefer.
I have an existing collectionView with a custom cell set up in interface builder and it's working fine. I've tried to find info on how to add a second one via interface builder but have had no luck in finding any.
What I've done:
I've created the second collectionView programatically in my viewDidLoad method. I have an instance variable named _collectionView2.
- (void)viewDidLoad
{
[super viewDidLoad];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
_collectionView2 = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
[_collectionView2 setDataSource:self];
[_collectionView2 setDelegate:self];
[_collectionView2 registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
[_collectionView2 setBackgroundColor:[UIColor redColor]];
[self.view addSubview:_collectionView2];
[_collectionView2 setHidden:YES];
I've modified delegate and datasource methods to make them aware about the new collectionView:
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if (collectionView == _collectionView) {
NSArray *people = [_thisController objects];
return [people count];
} else {
return 20;
}
}
.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
if (collectionView == _collectionView) {
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[[cell activityIndicator] startAnimating];
PFFile *userImageFile = [object valueForKey:#"image"];
[[cell imageView] setFile: userImageFile];
[[cell imageView] loadInBackground];
[[cell activityIndicator] stopAnimating];
[[cell title] setText:[object valueForKey:#"title"]];
[[cell price] setText:[NSString stringWithFormat: #"£%# GBP", [object valueForKey:#"price"]]];
return cell;
} else {
static NSString *CellIdentifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor greenColor];
return cell;
}
//_addToFavouritesButton = [cell addFavouriteButton];
[_addToFavouritesButton addTarget:_thisController action:#selector(addToFavouritesButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (collectionView == _collectionView2) {
return CGSizeMake(50, 50);
} else {
return CGSizeMake(140, 272);
}
}
Then I have a method that responds to the UISegmentControl being switched from grid style display to single file display:
- (void)displayTypeSegmentSelected
{
_selectedDisplayTypeIndex = [_displayTypeControl selectedSegmentIndex];
if (_selectedDisplayTypeIndex == 0) {
NSLog(#"Single file item view selected");
[_collectionView setHidden:YES];
[_collectionView2 setHidden:NO];
} else {
NSLog(#"Grid style view selected");
[_collectionView setHidden:NO];
[_collectionView2 setHidden:YES];
}
}
CollectionView2 is hidden initially then unhidden when the segment control is used. It looks like it's working because when I toggle a red background shows and I did set a red background when I created the collectionView but green cells aren't showing at all.
I can't seem to see where I've gone wrong. I'd like to get this working so I can replace the cell with my custom cell. The only difference between grid display and single file display is that single file display will be an enlarge cell that takes up the width of the view. All properties etc will be the exact same meaning I can use my current custom cell.
Why aren't my cells showing? I've followed a clear tutorial on creating a collectionView programmatically but still no cells are showing.
Update:
I put a log message in the if statement that checks what collectionView is present and it is only ever trigger for my _collectionView and not _collectionView2.
Help is appreciated.
Regards
Not sure this will fix your issue, but when I have 2 collection views in one controller, I use their tag's to determine which one I am dealing with in a given function, like cellForItemAtIndexPath.
I also have always used different cell identifiers for the two cells and usually different subclasses of uicollectionviewcell.
Where do you create the first collection view and set its delegate?
Reloading the table data in the method my UISegmentedControl triggers got the cells to show up.
In the UICollectionView, I've got a custom UICollectionViewCellClass, where prepareForReuse is overridden for default formatting staff.
I've got an NSMutableArray containing NSIndexPaths from didSelectItemAtIndexPath:.
In cellForItemAtIndexPath: I reformat the selected cells so they appear selected.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ButtonCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"ButtonCell" forIndexPath:indexPath];
NSString *title = self.ingredientsBook.names[indexPath.item];
cell.label.text = title;
if ([self isSelectedIndexPath:indexPath]){
cell.backgroundColor = [UIColor whiteColor];
cell.label.textColor = [UIColor blueColor];
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
self.searchButton.enabled = YES;
ButtonCell *cell = (ButtonCell *)[collectionView cellForItemAtIndexPath:indexPath];
[selectedCellIndexPaths addObject:indexPath];
NSLog(#"%#", selectedCellIndexPaths);
cell.backgroundColor = [UIColor whiteColor];
cell.label.textColor = [UIColor blueColor];
NSString *name = self.ingredientsBook.names[indexPath.item];
[self.selectedIngredientNames addObject:name];
}
The problem is that when I tap the first cell it's not possible to select the 16th or 17th.
Or if I tap the first three ones it's not possible to select the three last ones.
The didSelectItemAtIndexPath is not being called I suppose.
I feel that it has to be something really simple but I can't see it right now.
I tried to put NSLogsin shouldSelectItemAtIndexPath for understand if that method was called and the method is not being called at all. This happens when there's a distance of 16 cells between the selected one and the problematic one.
Here are other data source methods and isSelectedIndexPath:
-(BOOL)isSelectedIndexPath:(NSIndexPath *)indexPath{
for (NSIndexPath *test in selectedCellIndexPaths){
if (test == indexPath){
return YES;
}
}
return NO;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [self.ingredientsBook.names count];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"%#", indexPath);
return YES;
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
self.searchButton.enabled = ([[collectionView indexPathsForSelectedItems] count] > 0);
ButtonCell *cell = (ButtonCell *)[collectionView cellForItemAtIndexPath:indexPath];
cell.backgroundColor = [UIColor blackColor];
cell.label.textColor = [UIColor whiteColor];
[selectedCellIndexPaths removeObject:indexPath];
NSString *name = self.ingredientsBook.names[indexPath.item];
[self.selectedIngredientNames removeObject:name];
}
I found two problems. The prepareForReuse method seemed to be screwing things up, so I just deleted it. The main problem though, was the way you were implementing isSelectedIndexPath:. As soon as it finds the first selected item as you loop through the items, it returns YES and exits the loop. What you want to do, is just check if the indexPath is contained in the selectedCellIndexPaths array:
-(BOOL)isSelectedIndexPath:(NSIndexPath *)indexPath{
if ([selectedCellIndexPaths containsObject:indexPath]) {
return YES;
}else{
return NO;
}
}
Or, if you prefer to use a more succinct syntax, you can replace the if-else block with:
return ([selectedCellIndexPaths containsObject:indexPath])? YES : NO;
I recently faced the exact same issue with my app. UICollectionViewCell selection worked properly prior to iOS 8.3, subsequently I started to see some strange behaviour. Cells that were not actually selected would appear selected, other cells, seemingly at random could not be selected.
I had both custom setSelected and prepareForResuse methods implemented on a UICollectionViewCell subclass as such:
-(void)setSelected:(BOOL)selected
{
[super setSelected:selected];
if (selected)
{
[[self selectedIndicator] setHidden:NO];
}
else
{
[[self selectedIndicator] setHidden:YES];
}
}
-(void)prepareForReuse
{
[[self imageView] setImage:nil];
}
The prepareForReuse method simply reset an image view in the custom cell.
In my prepareForReuse method I did not make a call to [super prepareForReuse] (which according to the documentation does nothing by default). When I added the call to [super prepareForReuse] all selection worked as intended. Although Apple states the default implementation does nothing, they also recommend that super should be called. Following this recommendation solved my issue.
In iOS 10 I found that programmatically clearing a UICollectionView that had multiple-selection enabled was buggy when prefetching was enabled. And of course prefetch is on by default in iOS 10.