In my app I've written the following code in a UICollectionView cellForRowAtIndexpath like this [CollectionView Cell is a custom cell]
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CollectionViewCell *cell = (CollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"localMusicCell" forIndexPath:indexPath];
[[cell.downImageButton viewWithTag:indexPath.item] addTarget:self action:#selector(downImgClicked:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
and the target method like this:
-(void)downImgClicked:(UIButton*)button{
}
And there are four items in UICollectionView, but for first item only this target method is called and for rest of them its not even triggering why?
try alternate way
cell.downImageButton.tag = indexPath.item;
[cell.downImageButton addTarget:self action:#selector(downImgClicked:) forControlEvents:UIControlEventTouchUpInside];
call method like
-(void)downImgClicked:(UIButton*)button{
NSLog (#"selected index ==%#",button.tag);
}
try this way,
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! AllCollectionViewCell
cell.playBtn.tag = indexPath.row
cell.playBtn.addTarget(self, action: Selector("buttonAction:"), forControlEvents: .TouchUpInside)
return cell
}
target method:
func buttonAction(sender:UIButton!) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("SampleViewController") as! PlayViewController
self.navigationController?.pushViewController(controller, animated: true)
}
its working for me, hope its helpful
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = (CollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"localMusicCell" forIndexPath:indexPath];
cell.downImageButton.tag =indexPath.row;
[cell.downImageButton addTarget:self action:#selector(downImgClicked:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
and the target method like this:
-(void)downImgClicked:(UIButton*)button
{
long selectedBtnRow;
selectedBtnRow = button.tag;
NSLog (#"selected index ==>%ld",selectedBtnRow);
}
Related
I have a UICollectionView inside a UITableViewCell. You may refer the image at here
I would like to reload the collectionView if any update happen.
I have done some research and found this :
how to reload a collectionview that is inside a tableviewcell
Reloading collection view inside a table view cell happens after all cells in table view have been loaded
UICollectionView not updating inside UITableViewCell
I called the #IBOutlet weak var collectionView: UICollectionView! from UITableViewCell to UITableViewController at cellForRowAt.
Here is the code:
var refreshNow: Bool = false
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.allCardCell, for: indexPath) as! AllCardTableViewCell
if refreshNow {
cell.collectionView.reloadData()
refreshNow = false
}
cell.collectionView.collectionViewLayout.invalidateLayout()
return cell
}
If the user click Ok on UIAlertAction :
let alert = UIAlertController(title: "Success", message: "Card successfully added", preferredStyle: .alert)
let action = UIAlertAction(title: "Ok", style: .default) { (action) in
self.refreshNow = true
self.tableView.reloadData()
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
The reason why I put the refreshNow is to prevent the apps from lagging and slow. But still did not update if any changes happen.
The problem is the collectionView did not refresh. But when I debug, it was went through the cell.collectionView.reloadData().
The update/changes only happen when I restart the apps. I want it to be so called real-times update.
Any help is really appreciated and many thanks.
Image credit: How to use StoryBoard quick build a collectionView inside UITableViewCell
At end of your update add:
DispatchQueue.main.async() { () -> Void in
self.tableView.reloadData()
}
In your case, you should assign tag to your collection view in order to get access outside the cellForRowAt function.
This is how your function should look like:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.allCardCell, for: indexPath) as! AllCardTableViewCell
cell.collectionView.tag = 1234
return cell
}
and the action will reload it will access the collectionView by using the tag
let action = UIAlertAction(title: "Ok", style: .default) { (action) in
let collectionView = self.tableView.viewWithTag(1234) as! UICollectionView
collectionView.reloadData()
}
Also take note that cellForRowAt will keep reload the content based what you added inside it every time the cell appear. So, keep updating your data outside the cellForRowAt function.
Because you reused UITableViewCell so you must alway reload your UICollectionView. If you use refreshNow to reload UICollectionView, at the cell have refreshNow = false, UICollectionView will display like cell that it 's reused => wrong
Udate rep:
See , in picture uitableviewcell 1 will reuse at index 6. If you not reload content of cell (reload collectionview) it will display like uitableviewcell 1 at index 0
#import "AddPhotoViewController.h"
#import "PhotoTableViewCell.h"
#import "ShareTableViewCell.h"
#interface AddPhotoViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tblView;
#property (strong,nonatomic)NSMutableArray *arrImages,*arrIndexPath,*selectImages;
#end
#pragma mark - TableViewDelegate&DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *returnCell;
static NSString *cellIdentifier = #"CellOne";
static NSString *cellIdentifierTwo = #"CellTwo";
static NSString *cellIdentifierThree = #"CellThree";
if (indexPath.row == 0) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
} else if (indexPath.row == 1){
ShareTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierTwo forIndexPath:indexPath];
cell.viewMood.layer.cornerRadius = 5;
cell.viewPeople.layer.cornerRadius = 5;
[cell.viewMood layer].borderWidth = 1;
[cell.viewMood layer].borderColor = [UIColor colorWithRed:241.0/255.0 green:143.0/255.0 blue:48.0/255.0 alpha:1].CGColor;
[cell.viewPeople layer].borderWidth = 1;
[cell.viewPeople layer].borderColor = [UIColor colorWithRed:241.0/255.0 green:143.0/255.0 blue:48.0/255.0 alpha:1].CGColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
}else if (indexPath.row == 2){
PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierThree forIndexPath:indexPath];
cell.collView.dataSource = self;
cell.collView.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
}
return returnCell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
#pragma mark- UIImagePickerControllerDelegate
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
[_arrImages addObject:chosenImage];
PhotoTableViewCell *cell = [self.tblView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]];
[cell.collView reloadData];
[picker dismissViewControllerAnimated:YES completion:^{
}];
}
#pragma mark - CollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [_arrImages count];
}
- ( UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"CellCollection";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
UIImageView *imgView = [(UIImageView*)[cell contentView] viewWithTag:100];
UIImageView *imgViewTick = [(UIImageView*)[cell contentView] viewWithTag:200];
UIView *view = [(UIView*)[cell contentView] viewWithTag:300];
if (indexPath.row == 0){
imgViewTick.hidden = YES;
view.hidden = YES;
}
if ([_arrIndexPath containsObject:indexPath]) {
[_selectImages removeAllObjects];
view.hidden = NO;
view.alpha = 0.4;
imgViewTick.hidden = NO;
imgView.image = [_arrImages objectAtIndex:indexPath.row];
[_selectImages addObject:[_arrImages objectAtIndex:indexPath.row]];
NSLog(#"Pick images:%#",_selectImages);
}else{
view.hidden = YES;
imgViewTick.hidden = YES;
imgView.image = [_arrImages objectAtIndex:indexPath.row];
}
return cell;
}
In my controller, I parse data from a JSON array and use it to populate a collection view. The code below successfully loads and displays the data, but fails to create individual sections.
As such, how do I modify the code to create sections for each object within the array. For example, if my array has a count of 50 urls, how do I create one section for each?
let lastItem = self.photos.count
self.photos.addObjectsFromArray(photoInfos)
let indexPaths = (lastItem..<self.photos.count).map { NSIndexPath(forItem: $0, inSection: 0) }
dispatch_async(dispatch_get_main_queue()) {
self.collectionView!.insertItemsAtIndexPaths(indexPaths)
}
Most of the time the apps use one section and many items in this section so that's why it may be a bit harder to find how to do your way.
It's all about how you use this 2 functions of the datasource:
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return self.items.count
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
Normally you return 1 section and the array count in the numberOfItemsInSection, but if you switch them you would have exactly what you want.
here is my code is in Objective-C, I hope it'll helpful to you.
Here HeaderView is sub class of UICollectionReusableView.
Where you can set view as you like.
-(CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(320.0f, 32.0f);
}
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
HeaderView *view=(HeaderView *)[historyCollection dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"headerView" forIndexPath:indexPath];
NSArray *viewsToRemove = [view subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
view.backgroundColor=[UIColor clearColor];
self.month = [[UILabel alloc] initWithFrame:CGRectMake(0, 5, 320, 21)];
self.month.text =[[monthList objectAtIndex:indexPath.section] uppercaseString];
self.month.textColor = [UIColor greenColor];
self.month.font=[UIFont boldSystemFontOfSize:17];
[view addSubview:self.month];
return view;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView heightForHeaderInSection:(NSInteger)section {
if (section == 0) {
return 0;
}
else if (section == 1) {
return 40.0;
}
else {
return 50;
}
}
Here is the code that worked for me
create the header cell. To do which I created a custom cell class and a nib to do the customization of the cell in the graphic editor
In viewDidLoad
self.collectionView?.registerNib(UINib(nibName: "KlosetCollectionHeaderViewCell", bundle: nil), forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderCell")
Then you add the delegate function
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> KlosetCollectionHeaderViewCell {
println("entring viewforsuppplementaryElementofKind")
var headerCell = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "HeaderCell", forIndexPath: indexPath) as? KlosetCollectionHeaderViewCell
return headerCell!
}
This will put the HeaderCell in the SectionView of the PFCollectionView
The controls that show in the cell you add them to the xib file as well as the outlets and actions
I tried the follow code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
Pictograma *pictograma;
pictograma = [pictogramas objectAtIndex:indexPath.row];
AddPictogramaCell *newCell = [[AddPictogramaCell alloc] initWithFrame:CGRectMake(0, 0, 200, 200) pictograma:pictograma];
[newCell.nombre setText:pictograma.nombre];
[newCell.imageView setImage:[UIImage imageWithContentsOfFile:pictograma.path]];
newCell.pictograma = pictograma;
return newCell;
}
but I get the error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier - cells must be retrieved by calling -
dequeueReusableCellWithReuseIdentifier:forIndexPath:'
I don't use a storyBoard so I don't know how to tackle this.
Two things wrong here.
Firstly when you create your collectionView you need to add this line
[collectionView registerClass:[AddPictogramaCell class] forCellWithReuseIdentifier:#"YOUR_IDENTIFIER"];
Then in your collectionView:cellForItemAtIndexPath: you create your cell like this:
AddPictogramaCell *newCell = (AddPictogramaCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"YOUR_IDENTIFIER" forIndexPath:indexPath];
To register a cell for reuse in Swift, you would do the following when creating your CollectionView:
collectionView.registerClass(AddPictogramaCell, forCellWithReuseIdentifier: "YOUR_IDENTIFIER")
Then when getting your cell, you would have something like this:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:AddPictogramaCell = collectionView.dequeueReusableCellWithReuseIdentifier("", forIndexPath: indexPath) as! AddPictogramaCell
return cell
}
I am trying to add footer to the UICollectionView.
Following is my code,
UICollectionView is added through IB
IN viewDidLoad I register the footer,
[mCollectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:#"footer"];
And implemented following method
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *reusableview = nil;
if (kind == UICollectionElementKindSectionFooter) {
UICollectionReusableView *headerView = [mCollectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"footer" forIndexPath:indexPath];
[headerView addSubview:mFooterView];
reusableview = headerView;
}
return reusableview;
}
But My application keep crashing and below is the log,
*** Assertion failure in -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:], /SourceCache/UIKit/UIKit-2380.17/UICollectionView.m:2249
Any help is appreciated.
Thanks.
in your code why you are dequeing a header view and adding the footer to it ?
the normal implementation of this method is :
- (UICollectionReusableView *)collectionView:(UICollectionView *)theCollectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)theIndexPath
{
UICollectionReusableView *theView;
if(kind == UICollectionElementKindSectionHeader)
{
theView = [theCollectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"header" forIndexPath:theIndexPath];
} else {
theView = [theCollectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:#"footer" forIndexPath:theIndexPath];
}
return theView;
}
For Swift 4
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
var myView = UICollectionReusableView()
if kind == UICollectionView.elementKindSectionHeader {
myView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: myHeader, for: indexPath)
} else {
myView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: myFooter, for: indexPath)
}
return myView
}
Upon cell selection, I want to handle changing the cell appearance. I figured the delegate method collectionView:didSelectItemAtIndexPath: & collectionView:didDeselectItemAtIndexPath: is where I should edit the cell.
-(void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
DatasetCell *datasetCell =
(DatasetCell *)[collectionView cellForItemAtIndexPath:indexPath];
[datasetCell replaceHeaderGradientWith:[UIColor skyBlueHeaderGradient]];
datasetCell.backgroundColor = [UIColor skyBlueColor];
}
and
-(void)collectionView:(UICollectionView *)collectionView
didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
DatasetCell *datasetCell =
(DatasetCell *)[collectionView cellForItemAtIndexPath:indexPath];
[datasetCell replaceHeaderGradientWith:[UIColor grayGradient]];
datasetCell.backgroundColor = [UIColor myDarkGrayColor];
}
This works fine, except when the cell gets reused. If I select cell at index (0, 0), it changes the appearance but when I scroll down, there is another cell in the selected state.
I believe I should use the UICollectionViewCell method -(void)prepareForReuse to prep the cell for resuse (ie, set the cell appearance to non selected state) but its giving me difficulties.
-(void)prepareForReuse {
if ( self.selected ) {
[self replaceHeaderGradientWith:[UIColor skyBlueHeaderGradient]];
self.backgroundColor = [UIColor skyBlueColor];
} else {
[self replaceHeaderGradientWith:[UIColor grayGradient]];
self.backgroundColor = [UIColor myDarkGrayColor];
}
}
When I scroll back to the top, the cell at index (0, 0) is in the deselected state.
When I just used the cell.backgroundView property, to prevent this from happening was to:
-(void)prepareForReuse {
self.selected = FALSE;
}
and the selection state worked as intended.
Any ideas?
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
}
Framework will handle switching the views for you once you setup your cell's backgroundView and selectedBackgroundView, see example from Managing the Visual State for Selections and Highlights:
UIView* backgroundView = [[UIView alloc] initWithFrame:self.bounds];
backgroundView.backgroundColor = [UIColor redColor];
self.backgroundView = backgroundView;
UIView* selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
selectedBGView.backgroundColor = [UIColor whiteColor];
self.selectedBackgroundView = selectedBGView;
you only need in your class that implements UICollectionViewDelegate enable cells to be highlighted and selected like this:
- (BOOL)collectionView:(UICollectionView *)collectionView
shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (BOOL)collectionView:(UICollectionView *)collectionView
shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
{
return YES;
}
This works me.
UICollectionView has changed in iOS 10 introducing some problems to solutions above.
Here is a good guide:
https://littlebitesofcocoa.com/241-uicollectionview-cell-pre-fetching
Cells now stay around for a bit after going off-screen. Which means that sometimes we might not be able to get hold of a cell in didDeselectItemAt indexPath in order to adjust it. It can then show up on screen un-updated and un-recycled. prepareForReuse does not help this corner case.
The easiest solution is disabling the new scrolling by setting isPrefetchingEnabled to false. With this, managing the cell's display with
cellForItemAt didSelect didDeselect works as it used to.
However, if you'd rather keep the new smooth scrolling behaviour it's better to use willDisplay :
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let customCell = cell as! CustomCell
if customCell.isSelected {
customCell.select()
} else {
customCell.unselect()
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
//Don't even need to set selection-specific things here as recycled cells will also go through willDisplay
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as? CustomCell
cell?.select()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as? CustomCell
cell?.unselect() // <----- this can be null here, and the cell can still come back on screen!
}
With the above you control the cell when it's selected, unselected on-screen, recycled, and just re-displayed.
Anil was on the right track (his solution looks like it should work, I developed this solution independently of his). I still used the prepareForReuse: method to set the cell's selected to FALSE, then in the cellForItemAtIndexPath I check to see if the cell's index is in `collectionView.indexPathsForSelectedItems', if so, highlight it.
In the custom cell:
-(void)prepareForReuse {
self.selected = FALSE;
}
In cellForItemAtIndexPath: to handle highlighting and dehighlighting reuse cells:
if ([collectionView.indexPathsForSelectedItems containsObject:indexPath]) {
[collectionView selectItemAtIndexPath:indexPath animated:FALSE scrollPosition:UICollectionViewScrollPositionNone];
// Select Cell
}
else {
// Set cell to non-highlight
}
And then handle cell highlighting and dehighlighting in the didDeselectItemAtIndexPath: and didSelectItemAtIndexPath:
This works like a charm for me.
I had a horizontal scrolling collection view (I use collection view in Tableview) and I too faced problems withcell reuse, whenever I select one item and scroll towards right, some other cells in the next visible set gets select automatically. Trying to solve this using any custom cell properties like "selected", highlighted etc didnt help me so I came up with the below solution and this worked for me.
Step1:
Create a variable in the collectionView to store the selected index, here I have used a class level variable called selectedIndex
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCVCell *cell = (MyCVCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"MyCVCell" forIndexPath:indexPath];
// When scrolling happens, set the selection status only if the index matches the selected Index
if (selectedIndex == indexPath.row) {
cell.layer.borderWidth = 1.0;
cell.layer.borderColor = [[UIColor redColor] CGColor];
}
else
{
// Turn off the selection
cell.layer.borderWidth = 0.0;
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCVCell *cell = (MyCVCell *)[collectionView cellForItemAtIndexPath:indexPath];
// Set the index once user taps on a cell
selectedIndex = indexPath.row;
// Set the selection here so that selection of cell is shown to ur user immediately
cell.layer.borderWidth = 1.0;
cell.layer.borderColor = [[UIColor redColor] CGColor];
[cell setNeedsDisplay];
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCVCell *cell = (MyCVCell *)[collectionView cellForItemAtIndexPath:indexPath];
// Set the index to an invalid value so that the cells get deselected
selectedIndex = -1;
cell.layer.borderWidth = 0.0;
[cell setNeedsDisplay];
}
-anoop
What I did to solve this was to make the changes in the customized cell. You have a custom cell called DataSetCell in its class you could do the following (the code is in swift)
override var isSelected: Bool {
didSet {
if isSelected {
changeStuff
} else {
changeOtherStuff
}
}
}
What this does is that every time the cell is selected, deselected, initialized or get called from the reusable queue, that code will run and the changes will be made. Hope this helps you.
In your custom cell create public method:
- (void)showSelection:(BOOL)selection
{
self.contentView.backgroundColor = selection ? [UIColor blueColor] : [UIColor white];
}
Also write redefenition of -prepareForReuse cell method:
- (void)prepareForReuse
{
[self showSelection:NO];
[super prepareForReuse];
}
And in your ViewController you should have _selectedIndexPath variable, which defined in -didSelectItemAtIndexPath and nullified in -didDeselectItemAtIndexPath
NSIndexPath *_selectedIndexPath;
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
if (_selectedIndexPath) {
[cell showSelection:[indexPath isEqual:_selectedIndexPath]];
}
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[cell showSelection:![indexPath isEqual:_selectedIndexPath]];// on/off selection
_selectedIndexPath = [indexPath isEqual:_selectedIndexPath] ? nil : indexPath;
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[cell showSelection:NO];
_selectedIndexPath = nil;
}
Only #stefanB solution worked for me on iOS 9.3
Here what I have to change for Swift 2
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//prepare your cell here..
//Add background view for normal cell
let backgroundView: UIView = UIView(frame: cell!.bounds)
backgroundView.backgroundColor = UIColor.lightGrayColor()
cell!.backgroundView = backgroundView
//Add background view for selected cell
let selectedBGView: UIView = UIView(frame: cell!.bounds)
selectedBGView.backgroundColor = UIColor.redColor()
cell!.selectedBackgroundView = selectedBGView
return cell!
}
func collectionView(collectionView: UICollectionView, shouldHighlightItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
The problem you encounter comes from the lack of call to super.prepareForReuse().
Some other solutions above, suggesting to update the UI of the cell from the delegate's functions, are leading to a flawed design where the logic of the cell's behaviour is outside of its class. Furthermore, it's extra code that can be simply fixed by calling super.prepareForReuse(). For example :
class myCell: UICollectionViewCell {
// defined in interface builder
#IBOutlet weak var viewSelection : UIView!
override var isSelected: Bool {
didSet {
self.viewSelection.alpha = isSelected ? 1 : 0
}
}
override func prepareForReuse() {
// Do whatever you want here, but don't forget this :
super.prepareForReuse()
// You don't need to do `self.viewSelection.alpha = 0` here
// because `super.prepareForReuse()` will update the property `isSelected`
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.viewSelection.alpha = 0
}
}
With such design, you can even leave the delegate's functions collectionView:didSelectItemAt:/collectionView:didDeselectItemAt: all empty, and the selection process will be totally handled, and behave properly with the cells recycling.
you can just set the selectedBackgroundView of the cell to be backgroundColor=x.
Now any time you tap on cell his selected mode will change automatically and will couse to the background color to change to x.
Thanks to your answer #RDC.
The following codes works with Swift 3
// MARK: - UICollectionViewDataSource protocol
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//prepare your cell here..
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCell
cell.myLabel.text = "my text"
//Add background view for normal cell
let backgroundView: UIView = UIView(frame: cell.bounds)
backgroundView.backgroundColor = UIColor.lightGray
cell.backgroundView = backgroundView
//Add background view for selected cell
let selectedBGView: UIView = UIView(frame: cell.bounds)
selectedBGView.backgroundColor = UIColor.green
cell.selectedBackgroundView = selectedBGView
return cell
}
// MARK: - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return true
}
Changing the cell property such as the cell's background colors shouldn't be done on the UICollectionViewController itself, it should be done inside you CollectionViewCell class. Don't use didSelect and didDeselect, just use this:
class MyCollectionViewCell: UICollectionViewCell
{
override var isSelected: Bool
{
didSet
{
// Your code
}
}
}