How add cell to UICollectionView programatically without storyBoard - ios

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
}

Related

EXC_BAD_INSTRUCTION error in call to cellForItemAtIndexPath()

My code is crashing with the following error in the line that gets the cell at the index path and I'm having trouble debugging it.
Thread: EXC_BAD_INSTRUCTION(code=EXC_1386_INVOP,subcode=0x0)
var cell = CollectionViewCell()
for row in 0...self.collectionNetList.count {
let indexpath = NSIndexPath.init(forRow: row, inSection: 0)
self.cell = collectionView?.cellForItemAtIndexPath(indexpath) as! CollectionViewCell
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ CRASHES HERE ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
cell.dlImage.image=UIImage(named: "ted") //Modify the custom pictures on the cell
}
However in this method the same instruction works as expected.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.cell = collectionView.cellForItemAtIndexPath(indexPath)! as! CollectionViewCell
}
CollectionViewCell is not registered. You need to call registerNib method of UICollectionView to register the custom cell in viewDidLoad or viewWillAppear method.
if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
let aCell = cell as! CollectionViewCell
aCell.dlImage.image=UIImage(named: "ted")
}
Such a right

iOS cell add target not working properly?

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);
}

How can I get the class (custom) of cell selected in UITableView?

Usually I get my selected cell this way:
- (void)tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = (CustomCell*) [table cellForRowAtIndexPath:indexPath];
}
But in the code I'm working with, I may have many kind of cells in my table view. How can I get the class of my selected cell (if it's for example CustomCell or CustomCell2) ?
You can check the type of cell returned
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[CustomCell class]]) {
//do specific code
}else if([cell isKindOfClass:[CustomCell2 class]]){
//Another custom cell
}else{
//General cell
}
SWIFT 4
Just in case, if someone needed it. Get the instance of selected cell and then check it for required tableViewCell type.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = myCustomCell.cellForRow(at: indexPath)
else
{
return
}
/** MyCustomCell is your tableViewCell class for which you want to check. **/
if cell.isKind(of: MyCustomCell.self)
{
/** Do your stuff here **/
}
}

UICollectionView & custom UICollectionReusableView not works

I'm trying to use custom UICollectionReusableView (which has own class and XIB) in my UICollectionView header. But after fetching data in the place of header I have nothing.
My steps:
Registering class in viewDidLoad:
[self.collectionView registerClass:[CollectionViewHeader class]
forSupplementaryViewOfKind: UICollectionElementKindSectionHeader
withReuseIdentifier:#"HeaderView"];
Trying to show:
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *reusableView = nil;
if (kind == UICollectionElementKindSectionHeader) {
CollectionViewHeader *collectionHeader = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"HeaderView" forIndexPath:indexPath];
NSInteger section = [indexPath section];
id <NSFetchedResultsSectionInfo> sectionInfo = [fetchRecipes sections][section];
collectionHeader.headerLabel.text = #"bla-bla-bla";
reusableView = collectionHeader;
}
return reusableView;
}
Can anybody tell me what's wrong? )
Thanks for any advice
I think you are adding label to the xib. So you need to registerNib: for the header view instead of registerClass:
Register Your header nib/xib in the viewDidLoad section.
[self.collectionView registerNib: [UINib nibWithNibName:#"headerCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:#"headerCell"];
Create the custom supplementary view cell.
- (headerCollectionViewCell *)collectionView:(UICollectionView *)collectionViews viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *reusableView = nil;
if (kind == UICollectionElementKindSectionHeader) {
UINib *nib = [UINib nibWithNibName:#"headerCollectionViewCell" bundle:nil];
[collectionViews registerNib:nib forCellWithReuseIdentifier:#"headerCell"];
headerCollectionViewCell *collectionHeader = [collectionViews dequeueReusableCellWithReuseIdentifier:#"headerCell" forIndexPath:indexPath];
collectionHeader.titleLabel.text = #"What";
reusableView = collectionHeader;
}
return reusableView;
}
Just in case if someone needs solution.
You don't have to register the header class using
[self.collectionView registerClass:...]
Just use the layout's delegate method to return the header class.
Actually there is can be few reasons:
(most common)
If u add u'r suplementaryView in storyboard, and then try to register class
[self.stickyCollectionView registerClass:[<CLASSFORSUPPLEMENTARYVIEWW> class]
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:NSStringFromClass([<CLASSFORSUPPLEMENTARYVIEWW> class])];
u will get suplementaryView, but not that u create (standard one) - u will see obj, but actually this is will be like placeholder.
Example:
Here u can see that obj was created, but outlets are nil (in this simple case only one outlet - _monthNameLabel).
I see few times this problem also
If u create separate Obj for handling dataSourse/delegate for collectionView and add reference outlet to it, and in init methods try to register class to u'r outlet (assumed that view created in separate nib file), u will also receive same result like previous, but reason another - u'r outlet is nil here:
As solution u for example can use custom setter for this like:
#pragma mark - CustomAccessors
- (void)setcCollectionView:(UICollectionView *)collectionView
{
_collectionView = collectionView;
[self.stickyCollectionView registerNib:...];
}
as suggested by #Anil Varghese
u can use registerClass instead of registerNib
Xcode 9, Swift 4:
Register UICollectionReusableView in viewDidLoad
self.collectionView.register(UINib(nibName: "DashBoardCollectionReusableView", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "ReuseIdentifier")
class CustomFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributesForElementsInRect = super.layoutAttributesForElements(in: rect)
var newAttributesForElementsInRect = [UICollectionViewLayoutAttributes]()
for attributes in attributesForElementsInRect! {
if !(attributes.representedElementKind == UICollectionElementKindSectionHeader
|| attributes.representedElementKind == UICollectionElementKindSectionFooter) {
// cells will be customise here, but Header and Footer will have layout without changes.
}
newAttributesForElementsInRect.append(attributes)
}
return newAttributesForElementsInRect
}
}
class YourViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let headerNib = UINib.init(nibName: "HeaderCell", bundle: nil)
collectionView.register(headerNib, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderCell")
let footerNib = UINib.init(nibName: "FooterCell", bundle: nil)
collectionView.register(footerNib, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "FooterCell")
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderCell", for: indexPath) as! HeaderCell
return headerView
case UICollectionElementKindSectionFooter:
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FooterCell", for: indexPath) as! FooterCell
return footerView
default:
return UICollectionReusableView()
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 45)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 25)
}
}
You was registering correctly the class and the delegate is implemented correctly.
The problem can be that the flow layout (by default) is not configured for header view, do you have this line in the code or interface file?
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)_collectionView.collectionViewLayout;
flowLayout.headerReferenceSize = CGSizeMake(CGRectGetWidth(_collectionView.bounds), 100);

UICollectionView cell selection and cell reuse

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
}
}
}

Resources