I have a UICollectionView that is a calendar. It's a single scrollable row. Some of the dates are unselectable as there is no data for that date. Others ARE selectable as there IS data for that date.
When a date is selected the calendar underlines the selected date and scrolls it to the center of the UICollectionView.
When a date is tapped that doesn't have a date I have this function...
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return //is there data for the date at this indexPath?
}
This stops those cells being selected... however, it still deselects the previously selected cell.
There is a shouldDeselect function but I can't use this as I don't know the index of the tapped cell. So I can't determine whether the item should be deselected.
Is there a better way of doing this?
It simple works for me when I reload the collectionview:
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
// return true whithout reloadData when necessarily
collectionView.reloadData()
return false
}
I recently had the same issue and I ended up with the following approach:
Whenever user selects a cell, remember the position in a member variable:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
myMemberVariable = indexPath.row
}
After that call myCollectionView.reloadData()
In the cellForRowAtIndexPath: apply appropriate configuration for the cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCell.identifier, for: indexPath) as! MyCell
if indexPath.row == myMemberVariable && canSelectCellAt(indexPath: indexPath) {
cell.backgroundColor = .green
} else {
cell.backgroundColor = .gray
}
return cell
}
Where canSelectCellAt(indexPath: indexPath) is a function, which returns true for selectable cells and false for non-selectable cells,
I just came across this question (kind of late?) and to solve this you just need to use
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
Instead of shouldSelect
My solution:
collectionView.allowsMultipleSelection = true
and in didSelect, deselect all previously selected index paths:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.indexPathsForSelectedItems?.filter({ $0 != indexPath }).forEach { indexPath in
collectionView.deselectItem(at: indexPath, animated: false)
}
}
My solution is to enable multiple selection
collectionView.allowsMultipleSelection = true
and
func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
return false
}
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
i have collectionView. At the first launch I change color for first item to black. The problem is that when I select another item I want it to become black and first item become white. I use didSelectItemAtIndexPath and didDeselectItemAtIndexPath, but if i don't click the first item then I can't change it's color when I click another one. Can someone help me?
set a selectedindexpath and reload collection view according to selected index path.
class CollectionViewController: UICollectionViewController {
var selectedIndexPath : IndexPath?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifier", for: indexPath)
if indexPath == selectedIndexPath {
cell.backgroundColor = UIColor.black
} else {
cell.backgroundColor = UIColor.white
}
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndexPath = indexPath
collectionView.reloadData()
}
}
You can do it by following way.
Override the method in UICollectionViewCell Class like below
override var isSelected: Bool{
didSet{
if(self.isSelected){
yourView.backgroundColor = YourSelectedColor
}else{
yourView.backgroundColor = YourUnSelectedColor
}
}
}
No Need to do anything in didSelectItemAt or didDeSelectItemAt methods.
Your element from your data source array should somehow know about current state of cell. For example you can have property of your custom object:
var isSelected: Bool = false
in didSelectItemAt method first change every element's isSelected property to false and then for selected element set true and then reload data of collectionView
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
dataSourceArray.forEach { $0.isSelected = false }
dataSourceArray[indexPath.row] = true
collectionView.reloadData()
}
then in cellForRowAt change backgroundColor of cell depends on isSelected property of certain element in your data source array
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
...
cell.backgroundColor = dataSourceArray[indexPath.row] ? .black : .white
...
}
var selectedIndexPath = IndexPath?
Alternatively you can just save indexPath of selected cell as global variable
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndexPath = indexPath
collectionView.reloadData()
}
and then in cellForRowAt you can set backgroundColor of cell depends on condition if indexPath is equal to selectedIndexPath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
...
cell.backgroundColor = indexPath == selectedIndexPath ? .black : .white
...
}
I'm working in UICollectionViewCell for my mobile app. When you tap the crossBtn it should go to the first UICollectionViewCell with reloading the data but collectionView?.reloadData() makes the colours messed up and it doesn't stay to one colour at a time per cell. Instead it keeps showing multiple colours on the cells even though I've put the colour as UIColor.clear in didDeselectItemAt method.
How do I return back to the first UICollectionViewCell item when the user taps on crossBtn without reloading the whole UICollectionView?
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return filters.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.hamburgerLabel.text = filters[indexPath.item]
// cell.hamburgerImageView.image = filterImages[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.layer.backgroundColor = UIColor.gray.cgColor
setFilter(title: filterNames[indexPath.row])
toolBar.isHidden = true
yesAndNoToolBar.isHidden = false
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.layer.backgroundColor = UIColor.clear.cgColor
collectionView.deselectItem(at: indexPath, animated: true)
}
#IBAction func crossBtn(_ sender: UIBarButtonItem) {
toolBar.isHidden = false
yesAndNoToolBar.isHidden = true
collectionView?.reloadData()
let indexPath = self.collectionView.indexPathsForSelectedItems?.last ?? IndexPath(item: 0, section: 0)
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: UICollectionViewScrollPosition.centeredVertically)
}
You may not have to reload the data. You can use the following method to scroll to a given index path.
let indexPath = self.collectionView.indexPathsForSelectedItems?.last ?? IndexPath(item: 0, section: 0)
func scrollToItem(at indexPath: indexPath,
at scrollPosition: .centeredVertically,
animated: true)
Note that this is different from using selectItem(at:animated:scrollPosition:).
Also, you should set the colours etc that you are having trouble with in the cellForItem method, not in the selection and deselection delegate methods. This is because cells are reused, so the cell you are styling will be used in a different place when you scroll. Instead, use the selection and deselection methods to store the selected index path(s) in a variable (or array of multiple), then check this array in cellForItem to determine whether to apply selected styling to that cell. This is why collectionView.reloadData() messes things up for you!
I would like to create a list of horizontal buttons in UIcollectionview. Only one and only button can be selected and it will change to red color, problem is the selected cell will be reuse then there is possibility two red buttons appear in a same time, what should I do to solve this?
Store the index of the selected row in didSelectRow , then inside cellForItemAt
var selectedIndex = -1
var urlArr = [url1,url2,url3,url4,url5]
//
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIdentifier1, for: indexPath as IndexPath) as! imagesCollectionViewCell
let currentUrl = urlArr[indexPath.row]
if(indexPath.row == selectedIndex)
{
// red
}
else
{
// default
}
}
if you want to create multiple toggle cells in UICollectionView then use this.
var selectedIndexes = NSMutableArray()
var items = [item1,item2,item3,item4,item5]
in cellForItemAt method. check for selected indexPath.row
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: yourIdentifier, for: indexPath as IndexPath) as! yourCell
if selectedIndexes.contains(indexPath.row){
// make it active
}else{
// make it default
}
}
And in didSelectItemAt method add and remove indexPath from the Array to toggle the button of cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if selectedIndexes.contains(indexPath.row){
selectedIndexes.add(indexPath.row)
}else{
selectedIndexes.remove(indexPath.row)
}
collectionView.reloadData()
}
I'm trying to implement this feature: in my app, if I selected a cell in UICollectionView, then the borders becomes blue, and if I select another one, the previous should be deselected and borders should become transparent. There is methods that I've written:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ChatCell
/* Set some settings */
if globalSelected[indexPath.item] {
cell.circleView.layer.borderColor = UIColor.blue.cgColor
} else {
cell.circleView.layer.borderColor = UIColor.clear.cgColor
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//Global variable for maintain selection
global.selectedChatPath = indexPath
globalSelected[indexPath.item] = true
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if indexPath != nilPath {
globalSelected[indexPath.item] = false
collectionView.reloadData()
}
}
The nilPath is just IndexPath(item: -1, section: 0), but it doesn't matter, because collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) isn't even called. My CollectionView has allowSelection = true and allowsMultipleSelection = false properties. I will be thankful for any help.
If only a single cell is supposed to be selected at the same time I recommend to put the currently selected index path into a instance variable (nil means nothing is selected)
var selectedIndexPath : IndexPath?
In cellForItemAtset the colors depending on the instance variable
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ChatCell
/* Set some settings */
if let selected = selectedIndexPath, selected == indexPath {
cell.circleView.layer.borderColor = UIColor.blue.cgColor
} else {
cell.circleView.layer.borderColor = UIColor.clear.cgColor
}
return cell
}
In didSelectItemAt reload only the previous and new selected cells and set selectedIndexPath to the new selected index path. This is more efficient than reloading the entire collection view.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//Global variable for maintain selection
var cellsToReload = [indexPath]
if let selected = selectedIndexPath {
cellsToReload.append(selected)
}
selectedIndexPath = indexPath
collectionView.reloadItems(at: cellsToReload)
}
didDeselectItemAt is only needed if you want to deselect a cell explicitly.
Try this
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ChatCell
/* Set some settings */
if globalSelected[indexPath.item] {
cell.circleView.layer.borderColor = UIColor.blue.cgColor
collectionView.selectItemAtIndexPath(indexPath, animated: false, scrollPosition: .None)
}
else {
cell.circleView.layer.borderColor = UIColor.clear.cgColor
collectionView.deselectItemAtIndexPath(indexPath, animated: false)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CollectionViewCell
if selectedIndex == indexPath.item {
cell.backgroundColor = .blue
} else {
cell.backgroundColor = .clear
}
}
and in didSelectItemAt,
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath.item
collectionView.reloadData()
}
Reload your UICollectionView everytime a cell is selected and then change the border of the desired cell.
When you reload data the border for the previous cell is removed, after which you can add border to the cell that you want.
didDeselectItem won't be called until you tap the selected cell again. In order to deselect the previously selected cell when you tap another cell, you need to set the previously selected cell's setting in the global variable to false in didSelectItem like the following:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
globalSelected[global.selectedChatPath.item] = false //Set the previously selected cell's setting to false
//Global variable for maintain selection
global.selectedChatPath = indexPath
globalSelected[indexPath.item] = true
collectionView.reloadData()
}
I want to highlight the specific cell of the UICollectionView by default.
However I can not do that.The code is below.
UIView(in the init method):
let selectedIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: [])
UICollectionViewCell:
override var isSelected: Bool {
didSet {
imageView.tintColor = isSelected ? UIColor.white : UIColor.red
}
}
How can I select specific indexpath of a UICollectionView by default ?
If you need any other information to solve it, let me know.
Thank you.
This only selects the first cell
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//add here
let selectedIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: [])
}
You should override this method
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//you should set needed cells as selected here, or set it to deselected, because it is your responsibility now
var shouldSelect = false //or true
cell.isSelected = shouldSelect
}
But in this case you should keep track of collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) method and reload collection while selection.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.reloadData()
//or reload sections
}