I have an odd problem with my UICollectionView. When I select the first cell in my Collection View the last cell is highlighted but does not show up in the array of selected index paths. Why is this happening?
Here is the code I am using to select and deselect the cells?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
let cell = collectionView.cellForItem(at: indexPath)
cell?.layer.borderWidth = highlightedCellBorderWidth
cell?.layer.borderColor = UIColor.yellow.cgColor
selectedImages.append(imageArray[indexPath.item])
print(selectedImages)
print(collectionView.indexPathsForSelectedItems)
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.layer.borderWidth = 0
let index = selectedImages.index(of: imageArray[indexPath.item])
selectedImages.remove(at: index!)
print(selectedImages)
print(collectionView.indexPathsForSelectedItems)
}
You don't show how your cell is initialized, but...
collectionView(_:didDeselectItemAt:) is only called when the user successfully deselects an item. It does not get called if you deselect an item programmatically. If you are not resetting your layer's borderWidth when initializing a cell, the non-zero width will carry forward when the cell is reused by the collection view.
I found this link on S.O. which was helpful. To solve the problem I simply added the following code to my func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {}.
Code...
if collectionView.indexPathsForSelectedItems?.contains(indexPath) == true {
print("This cell is selected \(indexPath.item)")
cell?.layer.borderWidth = 3
cell?.layer.borderColor = UIColor.yellow.cgColor
}else{
cell?.layer.borderWidth = 0
}
Related
I have a Horizontal UICollectionView like the horizontal Calender in iOS.
Paging is enabled but not allowsMultipleSelection.
self.allowsMultipleSelection = false
self.isPagingEnabled = true
There are only 5 cells per page.
let cellSize = CGSize(width: self.view.frame.width / 5 , height: 60)
CollectionView's height is also 60.
didSelectItemAt change background color to .red and didDeselectItem resets it to .white.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = .red
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = .white
}
}
The collection view has multiple sections and rows. If I select a cell in the first visible page and scroll, random cells are selected in the next visible pages. That is to say random cells are red in the next pages. I do not want this to be so. I want to select/change color of cells manually.
How can I fix this?
Don't forget that UICollectionView has embedded reusing mechanism, so you should deselect your cells in the method "prepareToReuse" directly inside the cell class.
Take a class-level variable, say index
var index = -1
As you have said that multiple selections are not allowed so the following will do the job for you
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
index = indexPath.item
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = indexPath.item == index ? .red : .white
}
}
Whenever user tap on any cell we save the position in index variable and then call the reloadData() to notify collectionView about the change
In cellForRowAt we check if the current cell us selected we set the color to red otherwise white
First, if you want to preserve multiple selection, you have to remember your selected ones in an array since it would get lost if a cell gets recycled and reused. For that use something like a [IndexPath] type). If one selected cell is enough, you could use a non-array version of below code.
var selectedItems: [IndexPath] = []
Then, do your recoloring in your cell's cellForItemAt(:):
cell.backgroundColor = selectedItems.contains(indexPath) ? .red : .white
Your didSelectItemAt delegate function should look like:
if !selectedItems.contains(indexPath) { selectedItems.append(indexPath)}
collectionView.cellForItem(at: indexPath)?.backgroundColor = .red
and your didDeselectItemAt delegate function:
if let index = selectedItems.firstIndex(of: indexPath) { selectedItems.remove(at: index) }
collectionView.cellForItem(at: indexPath)?.backgroundColor = .white
This should actually work. Let me know if we have to do adjustments.
I have a collection view, and you can select the items in it and toggle them on and off by changing the background colour. The cells are toggled on/off thanks to a boolean I have in an arrow I made for all of the cells. I have saved the bool value but when I try to write them back into the array and use collectionView.reloadData()the app crashes. My collectionViewcode is:
extension OLLViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { //set the amount of items in the CollectionView to the amount of items in the OLLData dictionary
return OLLData.OLLCasesList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { //set each cell to a different mamber of the dict.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OLLCell", for: indexPath) as! OLLCell
cell.imageView.backgroundColor = OLLData.OLLCasesList[indexPath.item]._isSelected ? UIColor.orange : UIColor.clear //change colour if selected
let image = OLLData.OLLCasesList[indexPath.item]._imageName
cell.label.text = image
cell.imageView.image = UIImage(named: image)
let savedIsSelected = defaults.bool(forKey: Key.isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = savedIsSelected
//collectionView.reloadData() //when uncommented it crashes the app
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { //detect if case selected and reload CollectionView
let caseName = OLLData.OLLCasesList[indexPath.item]._imageName
print(caseName, OLLData.OLLCasesList[indexPath.item]._isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = !OLLData.OLLCasesList[indexPath.item]._isSelected
defaults.set(OLLData.OLLCasesList[indexPath.item]._isSelected, forKey: Key.isSelected)
collectionView.reloadItems(at:[indexPath])
collectionView.reloadData()
if OLLData.OLLCasesList[indexPath.item]._isSelected == true { //if the item is selected, add to selectedCases array
selectedCases.append(OLLData.OLLCasesList[indexPath.item]._id)
selectedCaseNames.append(OLLData.OLLCasesList[indexPath.item]._imageName)
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
else if OLLData.OLLCasesList[indexPath.item]._isSelected == false { //remove from selectedCases array
selectedCases.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._id })
selectedCaseNames.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._imageName })
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
}
._isSelectedis the boolean that says whether the cell is 'toggled'.
Any ideas would be greatly appreciated.
First of all, uncommenting that line will produce an infinite loop. cellForRowAt happens because the collection view is reloading, so calling a refresh while the collection view is refreshing is no good.
So your issue is that you don't know how to display selected cells in your collection view, right?
Here's a function that fires right before the collection view is about to display a cell:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
<#code#>
}
Inside this function, you should:
Cast cell into your OLLCell (safely if you want to be thorough)
Look at your data and see if the cell should be selected OLLData.OLLCasesList[indexPath.item]._isSelected
Ask your casted cell to change its colors/UI/appearance according to your ._isSelected boolean
Step 3 has a VERY important caveat. You should be changing the UI when ._isSelected is false AND when it's true. Because the collection view reuses cells, old UI state will randomly recur. So setting it every time is a good way to ensure the behavior you want.
Here's an example:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
//Cast the vanilla cell into your custom cell so you have access
//to OLLCell's specific functions and properties.
//Also make sure the indexPath falls in the indices of your data
if let myCastedCell = cell as? OLLCell,
0 ..< OLLData.OLLCasesList.count ~= indexPath.item
{
myCastedCell.imageView.backgroundColor = OLLData
.OLLCasesList[indexPath.item]._isSelected
? UIColor.orange
: UIColor.clear
}
}
I have looked through google and stack overflow and I was not able to find an answer. I have a UICollectionView and would like to take the user to another view upon the cell being clicked. But before doing so I would like to click the cell on the simulator and have the label's name printed in the console so I can from there figure out how to write the performSegue method. I am having issues with the didSelectItemAt function.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let clothesCell = collectionView.dequeueReusableCell(withReuseIdentifier: "clothesCell", for: indexPath) as! closetCollectionViewCell
clothesCell.clothingName.text = shirtStyle[indexPath.item]
clothesCell.clothingColor.text = shirtColor[indexPath.item]
clothesCell.clothingSize.text = "\(sizes[indexPath.item])"
return clothesCell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item.clothingName)
}
Use this for print in didselectItem for selected items
print(shirtStyle[indexPath.item])
You need to get the cell that was clicked at indexPath like this:
let cell = collectionView.cellForItem(at:indexPath) as! closetCollectionViewCell
Then just get the values from the cell variables and print.
I need to get the next cell inside cellForItem within a collection view so that I can update a view object. When I try the following below it doesn't work. I've also tried indexPathForVisibleItems passing in indexPath.row + 1 and the produces an index out of range error.
let index = IndexPath(row: indexPath.row + 1, section: indexPath.section)
if let nextCell = collectionView.cellForItem(at: index) as! MKRCell {
nextCell.setupWaitView(time: timeToWait)
nextCell.waitViewHeightConstraint.constant = 80
nextCell.waitView.alpha = 1
nextCell.waitView.isHidden = false
}
Is this possible to achieve or will I need to do this via another way?
Thanks
No, it is not possible to get the cell object before initialization in cellForItemAt but here
you can receive the call before displaying the cell from UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView,willDisplay cell: UICollectionViewCell,forItemAt indexPath: IndexPath) {
if let myCell = cell as? MKRCell {
}
}
AND
If you want to set up the cell you have to setup view in the UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
}
You should update the cell in:
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
Remember to modify only the cell you'll be returning from this method. Other cells might not have exist at that moment.
Alternatively you can keep a weak reference to the cell and update it when needed.
I have put UITableView in UICollectionView, and it has 4 items, but when I scroll UICollectionView to the last item, it shows the content of the first UITableView ( So when I press option 3 button it scrolls to the last item of collectionView but the content is wrong ) . You can have a look at the below picture.
Below is the link to my project, please help me out with this issue.
Link to my project
You have to reload the UITableView in cellForItemAt as mentioned below.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TableCollectionCell", for: indexPath) as! TableCollectionCell
cell.tableView.tag = indexPath.item
cell.tableView.reloadData()
return cell
}