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
...
}
Related
Have an issue where:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
is called when an item is selected, but I cannot change any cell properties from this method.
I created a new project with a stripped down UICollectionViewController to change the background color of a cell when selected. It also doesn't work. Here it is:
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = UIColor.blue
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = self.collectionView(self.collectionView, cellForItemAt: indexPath)
cell.backgroundColor = UIColor.green
}
}
The only thing I did in Storyboard is delete the standard View Controller and replace it with a UICollectionViewController, create a subclass of UICollectionViewController and set the controller in the storyboard as that class.
Also, I can confirm that the cell's index path is returned from this method when called from inside the didSelectItemAt method:
self.collectionView.indexPathsForSelectedItems
You are using the wrong API. Never call the delegate method collectionView(_ cellForItemAt:, use cellForItem(at:
if let cell = collectionView.cellForItem(at: indexPath) {
cell.backgroundColor = UIColor.green
}
But be aware that this change is not persistent. When the user scrolls the color will change back to blue.
You can achieve that as below,
ViewController
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ColoursViewCell", for: indexPath) as! ColoursViewCell
return cell
}
UICollectionViewCell
class ColoursViewCell: UICollectionViewCell {
#IBOutlet var photoImageView: UIImageView?
override var bounds: CGRect {
didSet {
self.layoutIfNeeded()
}
}
override var isSelected: Bool{
didSet{
if self.isSelected{
self.photoImageView?.backgroundColor = UIColor.random
}else{
self.photoImageView?.backgroundColor = UIColor.lightGray
}
}
}
}
you can have sample projects from this link I have in GitHub
https://github.com/hadanischal/RandomColors/tree/develop
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 am using Swift4 and Xcode9.
I create my collectionView cells by using the delegate method below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
return cell
}
Then, I want to replace the image of selected cell with a "stop" image. To achieve this, I have tried to use didSelectItemAt method;
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (mediaPlayer?.isPlaying == true) {
// stop the player
mediaPlayer?.stopPlayingMedia()
} else {
// change the image and start playing
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell // CRASH
cell.mediaItemCellImageView.image = UIImage(named: "stop.png")
...
...
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
// stop playing
mediaPlayer?.stopPlayingMedia()
}
Now, when I select the cell which is already selected (hence the original image has already been replaced with "stop.png") the code above works; i.e. the original image is restored, which is done by stopPlayingMedia() method.
When I select a cell different than the currently selected one, the App crashes. I tried to move the didSelectItemAt call out of the delegate method, but it did not work.
Surprisingly, the logical check if collectionView.cellForItem(at: indexPath) is MediaItemCollectionViewCell succeeds when I select the currently selected cell, and fails when I select another cell.
I want to change the image of the cell, hence need to cast the variable type but it fails. Why does to cast fail when I select a different cell than the currently selected one?
Thanks
Edit:
I use collectionView.reloadData() to restore all the images to originals (I mentioned this in the OP - when stopPlayingMedia() is invoked). It appears that, if I add a private var index:IndexItem? to my ViewController class and update its value within didSelectItemAt (to save the last selected cell) and use the code below, the code does not crash
extension ViewController:MediaPlayerControllerDelegate {
// this delegate method is invoked when stopPlayingMedia() is called
func mediaPlayerControllerDidFinishPlaying() {
// restore the UI
// collectionView.reloadData() -> this causes the crash, instead, I only restore one cell
let cell = collectionView.cellForItem(at: index!) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: (index?.row)!)?.image
timeLabel.text = "00:00"
progressBar.progress = 0.0
}
}
I tried the following to create cell and changing the image inside cell on selection. Not crashing at all. Please check the implementation, It might help you.
import UIKit
class ViewController: UIViewController {
private var isPlayingModel = [false, false, false, false, false] // initially all are stop, not playing
#IBOutlet weak var collectionView: UICollectionView! {
didSet{
collectionView.delegate = self
collectionView.dataSource = self
}
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return isPlayingModel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
isPlayingModel[indexPath.row] = !isPlayingModel[indexPath.row]
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width, height: 60)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
}
class MediaItemCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageVw: UIImageView!
}
Check the implementation here.
You are not passing indexPath correctly to load a cell, that's why it is crashing.Edit your didSelectItemAt: method as follows:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedCell = IndexPath(item: indexPath.item, section: indexPath.section)
let cell = collectionView.cellForItem(at: selectedCell) as! ACollectionViewCell
cell.imgView.image = UIImage(named: "3.jpg")
}
Collection view indexPath has element item, and not row
Try this and see: (let me know what does it print)
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
if let cell = collectionView.cellForItem(at: indexPath) as? MediaItemCollectionViewCell {
if let cellImage = mediaPlayer?.item(at: indexPath.item)?.image { // Collection view indexPath has element `item`, and not `row`
cell.mediaItemCellImageView.image = cellImage
} else {
print("cellImage not found at indexPath - \(indexPath)")
}
} else {
print("collection view cell `MediaItemCollectionViewCell` not found at indexPath -\(indexPath) ")
}
// stop playing
mediaPlayer?.stopPlayingMedia()
}
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 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
}