Swift - Selecting item in Collection View - ios

I'm having a problem with selected items in a collection view.
Selected items change backgroundColor to blue, but it seems like the reusable cells are also affected.
my code looks like this:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell?
cell?.contentView.backgroundColor = UIColor.blueColor()
func collectionView(collectionView: UICollectionView, didDeslectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell?
cell?.contentView.backgroundColor = UIColor.blackColor()
}
func collectionView(collectionView: UIControllerView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: boxCell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell", forIndexPath: indexPath) as boxCell
cell.cellTitle.text = name[indexPath.row]
}
When I run the application, the selection works, selecting another cell, deselects the other selected cells, but when I scroll, the reusable cells are also turning blue.
I am using a horizontal scroll direction with only 1 row and 4 cells per row.
Where did I go wrong? Anybody else have had this issue?

It's normal behavior - the reason for that is that the items are reused, and after reusing they go through cellForItemAtIndexPath where you are not setting background color, so they keep last one you ever set - in your case didSelect method.
You should create a NSSet where you will keep all selected NSIndexPath's and add / remove NSIndexPath to it when selecting / deselecting.
Setting background color logic should be done in cellForItemAtIndexPath - after you check if NSIndexPath is existing in your NSSet you set a desired color.
On selection you will have to also reload certain element, so it will call cellForItemAtIndexPath for the one you clicked.

Use the prepare for resue method in your cell class.It will work fine.
override func prepareForReuse()
{
self.backgroundColor = UIColor.lightGrayColor()
}

Related

What goes on behind the scenes in .reloadData() in terms of cell indexPath?

I'm using a collection view and every time I select the cell I change the cell background color to red. Simple enough:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)! as! CustomCell
cell.backgroundColor = .red
}
This works absolutely fine. When I select the top 3 cells going from left to right, the background color changes exactly as I expect:
However If I reload the collectionView after I select the cell the selection ordering begins to behave strangely. When I select the same top 3 cells in the same order from left to right, different cells become selected:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)! as! CustomCell
cell.backgroundColor = .red
collectionView.reloadData()
}
Apple's documentation is cryptic. https://developer.apple.com/documentation/uikit/uicollectionview/1618078-reloaddata
They say that "This causes the collection view to discard any currently visible items (including placeholders) and recreate items based on the current state of the data source object. " But this makes me think that upon calling reloadData() the collectionViewCells would go back to gray and not jump indexPaths.
Can anyone explain what is going on in reloadData() to make the cell selection at index path ordering so strange?
First You need to use dequeue cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:"CustomCell", for: indexPath) as! CustomCell
And in cell you can use
-(void)prepareForReuse {
// Set default implementation
}

cell deselection needs double click

I am getting trouble to deselect a cell in one click. As I click the cell, the label text inside it gets highlighted, and when i deselect the cell, 1st click deselects the highlighted label text color, turning it to black and then a 2nd click is needed to deselect the cell and leaving a trail of white colored text behind...I tried to disable the highlight feature of a label, but i am stuck here.You can see the setting for the label where it states color is white when highlighted.
I only implemented this func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
I also tried to set dateLabel.isHighlighted = false, but i am getting Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional
I have a dictionary with boolean value to track the state of a cell being selected or not, but this method doesnt solve my problem
Any help is appreciated and wish yall happy new years.
update:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//addToList[currentMonthIndex].append(indexPath)
//dayCollectionView.deselectItem(at: indexPath, animated: true)
let cell = collectionView.cellForItem(at: indexPath) as! CustomCell
cell.dateLabel.isHighlighted = false
cell.dateLabel.isUserInteractionEnabled = false
}
I have successfully disabled both but i still need double click to deselect it. It seems like the label inside would take one click. I am sure of this because otherwise the cell would have been deselected on the 2nd click as my global array that tracks the cell selection status would have been changed by that click.
Two things:
In your UICollectionViewCell subclass:
override var isHighlighted: Bool {
willSet {
if newValue { // set selected state
} else { // set deselected state
}
}
}
And then I only implemented this
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
delegate call:
cell.isSelected = !cell.isSelected

Collection View Cells alpha is changing when it is not supposed to

I basically have a collection view where the cells have a view and that view's alpha decreases when the cell is tapped on. For some reason, when I scroll in the collection view, other cells views are also changing alphas and then the original cell that I selected also changed back. It has something to do with the cellForRowAtIndexPath method, but I'm not entirely sure what the issue is. Here is my code:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("chooseSpace", forIndexPath: indexPath)as! ChooseSpaceCell
let space = spaces2[indexPath.row]
cell.serviceLabel.text = spaces2[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! ChooseSpaceCell
cell.mask.alpha = 0.7
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! ChooseSpaceCell
cell.mask.alpha = 0.25
}
Originally, all the alphas start out at 0.25, change to 0.7 when tapped, and change back when deselected. This is a huge issue so any help would be much appreciated.
When you call
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("chooseSpace", forIndexPath: indexPath)as! ChooseSpaceCell
You are requesting a cell from the collection view. If there is a cell available it will reuse a cell that has already been created. If none are available it creates a new cell.
This means that when you scroll your collection view it is reusing the same cells that you used for previous items. If those items had there opacity changed the new items using that cell will have the same opacity.
You need to add an opacity field to your model or an attribute that will help you compute the opacity.

Why won't UICollectionViewCells that are offscreen update?

I have a UICollectionView displaying custom cells in a horizontal flow layout; in other words, some content is placed outside the screen bounds.
Additionally, I have a gesture that fires an NSNotification leading to a color change of some of the elements of my cells (i.e. a theme). Everything works perfectly except for the fact that the cells that are present out of the bounds of the screen don't all update to the new color change. Is there any way to force them to redraw?
In the function called when the NSNotification is fired I've tried redrawing the collection view with self.collectionView.reloadData(), self.collectionView.setNeedsDisplay() and self.collectionView.setNeedsLayout but to no avail. I tried the last two of the list in the awakeFromNib() of the custom cell class but nothing.
Here is the code for my cellForItemAtIndexPath:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = popularCollectionView!.dequeueReusableCellWithReuseIdentifier("popular", forIndexPath: indexPath) as! PopularPainting
cell.thumbnail.image = paintings[indexPath.row].paintingImage
cell.name!.text = paintings[indexPath.row].paintingName
cell.price!.setTitle(paintings[indexPath.row].paintingPrice, forState: UIControlState.Normal)
if cell.isDark {
cell.name!.textColor = UIColor(red: 205/255, green: 205/255, blue: 205/255, alpha: 1)
cell.price!.setTitleColor(self.kPaleBlue, forState: .Normal)
self.popularCollectionView.reloadData()
}
return cell
}
Any suggestions?
Note: Scrolling to the offscreen content and repeating the gesture to change themes works perfectly so I have no idea what's up.
Your assumption that there are cells that exist off-screen for every item in your collection view is incorrect. In fact, table views and collection views re-use cells that get scrolled off-screen for new cells coming on-screen, so there are only ever just over a screenful of cells in existence.
You're right to call reloadData after the notification fires. But you need to ensure your implementation of collectionView:itemForRowAtIndexPath: will correctly configure cells that are scrolled on-screen subsequently. That will probably mean saving the state change in a property after the notification fires, and checking that property when you configure cells in collectionView:itemForRowAtIndexPath:.
To get around the "view cannot be reached using cellForItemAtIndexPath:, but will not be recycled before being shown on-screen" issue, you can move the view initialization logic from collectionView:cellForItemAtIndexPath: to collectionView:willDisplayCell:forItemAtIndexPath:.
For example, where you originally had:
override func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: self.reuseIdentifier,
for: indexPath
)
// Initialize cell
return cell
}
You can replace it with:
override func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(
withReuseIdentifier: self.reuseIdentifier,
for: indexPath
)
}
override func collectionView(
_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath
) {
// Initialize cell
}
This ensures that if cellForItemAtIndexPath: returns nil, the cell will be properly initialized by collectionView:willDisplayCell:forItemAtIndexPath: the next time before it is shown on screen.

Scrolling in UICollectionView selects wrongs cells - Swift

I have the following UICollectionView which is populated by an Array with NSManagedObject of type Categories
The problem is that when a Cell is selected scrolling does not function correctly. When scrolling through the UICollectionView other cells get selected, and deselected. Strange behaviour. I think this is because of the indexPath that is set incorrectly after scrolling? Anyway, I have been struggling with this for a couple of hours, and cannot seem to grasp it. Hopefully someone can point me in the right direction!
The fetchedCategory gets compared to the category to check if it is already selected and if they are the same the colors are inverted.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("CategorySelectionCollectionCell", forIndexPath: indexPath) as CategoryCollectionViewCell
if fetchedCategories[indexPath.row] == category {
cell.categoryLabel?.text = fetchedCategories[indexPath.row].name
cell.categoryLabel?.textColor = UIColor.whiteColor()
cell.backgroundColor = fetchedCategories[indexPath.row].iconColor as? UIColor
collectionView.selectItemAtIndexPath(indexPath, animated: true, scrollPosition: UICollectionViewScrollPosition.None)
} else {
cell.categoryLabel?.text = fetchedCategories[indexPath.row].name
cell.categoryLabel?.textColor = fetchedCategories[indexPath.row].iconColor as UIColor
collectionView.deselectItemAtIndexPath(indexPath, animated: true)
}
return cell
}
func collectionView(collectionView: UICollectionView!, didSelectItemAtIndexPath indexPath: NSIndexPath!) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as CategoryCollectionViewCell
cell.categoryLabel?.textColor = UIColor.whiteColor()
cell.backgroundColor = fetchedCategories[indexPath.row].iconColor as? UIColor
category = fetchedCategories[indexPath.row]
}
func collectionView(collectionView: UICollectionView!, didDeselectItemAtIndexPath indexPath: NSIndexPath!) {
if var cell = collectionView.cellForItemAtIndexPath(indexPath) as? CategoryCollectionViewCell {
cell.categoryLabel?.text = fetchedCategories[indexPath.row].name
cell.categoryLabel?.textColor = fetchedCategories[indexPath.row].iconColor as UIColor
cell.backgroundColor = UIColor.whiteColor()
}
}
You don't want to call cellForItemAtIndexPath and configure the cells in the didSelectItemAtIndexPath or didDeselectItemAtIndexPath delegate methods.
Also, you shouldn't be calling selectItemAtIndexPath and deselectItemAtIndexPath from within the cellForItemAtIndexPath method.
Instead, just keep track and toggle the state of the selected category in your select/deselect callbacks, and then don't do anything other the set up up the look of your cells in cellForItemAtIndexPath.
As the commenter pointed out, the cells are re-used, so stick to the simple way the delegate callbacks are designed to be used, and you should have much better luck.
If you need to refresh the look of the cells, do it by relying on cellForItemAtIndexPath being called while scrolling and using the reloadData and reloadItemsAtIndexPaths collection view methods if you need to force an update.

Resources