UIcollectionview list of horizontal toggle button in Swift - ios

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

Related

Swift make select/unselect in single/multiple selection mode for UICollectionView inside UITableViewCell

I am new to iOS and I want to implement a UICollectionView inside a UITableView which can have multiple select/unselect in section 1 of the UITableview. And in section 2, only a single selection is allowed. And save the selection sate event if I dismiss the Viewcontroller and when I open it again, it must show the last selected cell as highlight.
I searched for tutorials but all of them don't mention to select/unselect the state of collection cell or saving state after dismissing view controller.
Can someone help to implement it?
Thank in advance!
Here is my code I do till now:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clvData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "clvCell", for: indexPath) as! demoCollectionViewCell
cell.title.text = clvData[indexPath.item] as? String
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .red
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .white
}
you guys can also check my project here:
https://mega.nz/#!xRs0EQyQ
try this and let me know if you have any problem or if this solved your problem.
var arrSelectedIndex:[IndexPath] = []// store this array either in database by api or in local
var clvData:[String] = []// your data array
//get the arrSelectedIndex from default in viewDidLoad before reloading the table and collection.
override func viewDidLoad() {
super.viewDidLoad()
if let myArray = UserDefaults.standard.array(forKey: "selectedArray") as? [IndexPath] {
arrSelectedIndex = myArray
} else {
arrSelectedIndex = []
}
}
// and save the arrSelectedIndex in viewWillDisappear method
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UserDefaults.standard.set(arrSelectedIndex, forKey: "selectedArray")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clvData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "clvCell", for: indexPath) as! demoCollectionViewCell
cell.title.text = clvData[indexPath.item] as? String
if arrSelectedIndex.contains(indexPath) { // You need to check wether selected index array contain current index if yes then change the color
cell.backgroundColor = UIColor.red
}
else {
cell.backgroundColor = UIColor.white
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .red
if !arrSelectedIndex.contains(indexPath) {// if it does not contains the index then add it
arrSelectedIndex.append(indexPath)
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .white
if let currIndex = arrSelectedIndex.firstIndex(of: indexPath) {// if it contains the index then delete from array
arrSelectedIndex.remove(at: currIndex)
}
}

Can't deselect item in collectionView [closed]

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

Unhide the selected cells' 'tick' in a collectionView

I have a collectionView of images and I want to show when multiple of these cells are selected.
I use a simple cell.myUIImage.isHidden = false but nothing seems to work.
Multiple select is enabled in the Interface Builder and is set in viewDidLoad(), this is what I have:
#IBOutlet weak var myUIImage: UIImageView!
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoItemView", for: indexPath) as! PhotoFeed
cell.myUIImage.isHidden = false
print ("cell was selected at:", indexPath.item)
photoCollection.reloadData()
}
You can not access UICollectionViewCell with dequeueReusableCell other than cellForItemAt indexPath method.
To access cell in didSelectItemAt indexPath use:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let collectionCell = collectionView.cellForItem(at: indexPath) as? PhotoFeed {
collectionCell.myUIImage.isHidden = false
print ("cell was selected at:", indexPath.item)
photoCollection.reloadData()
}
}
But again photoCollection.reloadData() will reload the cell and myUIImage will be hidden again.
So you need to maintain the selected indexes and show/hide in cellForItemAt indexPath. With the help of selectedIndexes you can have the functionality like show image on selection and hide image on select again.
You should have an array of selectedIndexes. Say
var selectedIndexes = [IndexPath]()
Add in didSelectItemAt indexPath, Update method as:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if selectedIndexes.contains(indexPath) {
if let index = selectedIndexes.index(of: indexPath) {
selectedIndexes.remove(at: index)
}
} else {
selectedIndexes.append(indexPath)
}
photoCollection.reloadData()
}
In cellForItemAt indexPath:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {\
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoItemView", for: indexPath) as? PhotoFeed else {
fatalError("cell not found")
}
if selectedIndexes.contains(indexPath) {
cell.myUIImage.isHidden = false
} else {
cell.myUIImage.isHidden = true
}
return cell
}
You should create #IBOutlet for myUIImage in collectionviewcell:
class PhotoFeed: UICollectionViewCell {
#IBOutlet weak var myUIImage: UIImageView!
}
In ViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoItemView", for: indexPath) as! PhotoFeed
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! PhotoFeed
cell.myUIImage.isHidden = !cell.myUIImage.isHidden
}

UICollectionViewCell Deselect Pre-Selected Cell on First Tap

I am modally presenting a viewcontroller that hosts a viewcollection with "pre-selected" cells formatted to a different background color based on data passed on segue.
When I tap on one of these "pre-selected" cells, it takes two taps to trigger the didDeselectItemAt delegate method. I understand why this is happening while debugging, where the cell although of different color is not necessarily recognized in a selected state. Is there any way to trigger didDeselectItemAt first for the "pre-selected" cells?
I've tried, within the delegate method cellForItemAt, to incorporate, as part of a conditional statement that changes the cell background color, setting cell.isSelected = true. Similarly within the same delegate method, I've also tried invoking a function that would invoke the delegate method didSelectItemAt with indexPaths of these "pre-selected" cells. Both produced the same result.
Below is (abbreviated) relevant code snippets:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! ExampleCollectionViewCell
if preselectedDataPoints { cell.backgroundColor = blue }
else { cell.backgroundColor = white }
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = blue
preselectedDataPoints.append(newDataPoint)
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = white
preselectedDataPoints.remove(at: existingDataPoint)
}
Programatically call collectionView.deselectItem(at: indexPath, animated: true) in didSelectItem if cell is preselected.
Refrer code
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if preselectedDataPoints {
collectionView.deselectItem(at: indexPath, animated: true)
}else{
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = blue
preselectedDataPoints.append(newDataPoint)
}
}
or
Directly call what ever code you need to execute in deSelect method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if preselectedDataPoints {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = white
preselectedDataPoints.remove(at: existingDataPoint)
}else{
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = blue
preselectedDataPoints.append(newDataPoint)
}
}

didDeselectItemAt indexPath doesn't triggered when another cell is selected

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

Resources