How can I save the selected state of UICollectionView inside an UITableViewCell?
For more details, I have an UITableView with 5 sections and each section has only 1 cell, I put another UICollectionView into the cell of the table view and whenever I select an item of collection view cell it will highlight with a red background.
Now I wanna save the selection state for the collection view even if I dismiss the view controller then open it again it must display the correct selected item and I think I will use UserDefaults for saving. But I noticed that when I select an item of collection view in another section it always saves the same index with the first section of the table view.
Here is my code for saving the selected index path to an array, can you please tell me where's my mistake:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let strData = itemFilter[indexPath.section].value[indexPath.item]
let cell = collectionView.cellForItem(at: indexPath) as? SDFilterCollectionCell
cell?.filterSelectionComponent?.bind(title: strData.option_name!, style: .select)
cell?.backgroundColor = .red
cell?.layer.borderColor = UIColor.white.cgColor
arrSelectedIndex.append(indexPath)
}
and when deselect:
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let strData = itemFilter[indexPath.section].value[indexPath.item]
let cell = collectionView.cellForItem(at: indexPath) as? SDFilterCollectionCell
cell?.filterSelectionComponent?.bind(title: strData.option_name!, style: .unselect)
cell?.backgroundColor = .white
cell?.layer.borderColor = UIColor.black.cgColor
if arrSelectedIndex.count > 0 {
arrSelectedIndex = arrSelectedIndex.filter({$0 != indexPath})
}else {
arrSelectedIndex.removeAll()
}
}
As you mention you want to save arrSelectedIndex in userdefault, so get arrSelectedIndex from userdefault.
if you have collectionView in each section of UITableView then with arrSelectedIndex save the indexpath of table section as well.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.cellForItem(at: indexPath) as? SDFilterCollectionCell else {
return UICollectionViewCell()
}
// color the background accordingly
if arrSelectedIndex.contains(indexPath) {
// selected state
cell.backgroundColor = .red
} else {
// non-selected state
cell.backgroundColor = .white
}
cell.layer.borderColor = UIColor.white.cgColor
return cell
}
Related
I have a UICollectionView within a View Controller. When the cell is clicked it updates the image associated with the cell. However, when a cell isSelected and then scrolled out of view and another cell is selected, it appears as the isSelected == true image until it is brought out and back into view once more. Images below hopefully describe the action better.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CustomCollectionViewCell
// Configure the cell
cell.layoutIfNeeded()
if cell.isSelected {
let image = imageArray1[indexPath.row]
cell.cellImage.image = image
return cell
} else {
let image = imageArray2[indexPath.row]
cell.cellImage.image = image
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell else {
return
}
// deselect unused
cell.isSelected = false
cell.cellImage.image = imageArray2[indexPath.row]
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CustomCollectionViewCell
cell?.isSelected = true
cell.cellImage.image = imageArray1[indexPath.row]
}
Step 1: First cell is selected and changes to red image.
Step 2: Selected first cell is out of view and 5th cell is now selected, which should make cell 1 turn back to grey image.
Step 3: first cell should be unselected but when it is brought back into view it is still showing as selected. It will change back to grey image if it goes out and back into view once more.
I have a variabel selectedIndexPath, which gets the indexPath selected from previous view controller. I am getting the required backgroundColor for cell in the collection view of present view controller. But when I select another cell in the collection view, the selected cell of previous view controller remains same without being deselected. So, now I have two cells with the background color. Following is my code
var selectedIndexPath = IndexPath()
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.allowsMultipleSelection = false
self.collectionView.allowsSelection = true
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! TestCollectionViewCell
if (indexPath == selectedIndexPath)
{
cell.backgroundColor = UIColor.white
}
else
{
cell.backgroundColor = UIColor.clear
}
collectionView.allowsMultipleSelection = false
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = UIColor.white
collectionView.allowsMultipleSelection = false
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath)
{
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = UIColor.clear
collectionView.allowsMultipleSelection = false
}
How do I deselect the cell in cellForItemAt, when I select new cell in didSelectItemAt. Thanks in advance.
First of all set allowsMultipleSelection in Interface Builder and remove all redundant occurrences of setting it in code
You have to update the selectedIndexPath variable. Manipulating the cell directly is always a bad idea.
Reloading the cells is much more reliable.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
guard selectedIndexPath != indexPath else { return }
let indexPathsToUpdate = [selectedIndexPath, indexPath]
selectedIndexPath = indexPath
tableView.reloadRows(at: indexPathsToUpdate, with: .none)
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath)
{
guard selectedIndexPath == indexPath else { return }
selectedIndexPath = IndexPath()
tableView.reloadRows(at: [indexPath], with: .none)
}
There is only one problem: If you want to have an empty selection you have to declare selectedIndexPath as optional and handle it properly.
First of all, you don't need to override didDeselect for this. All you need to do is deselect the previously selected item while selecting the current item in didSelectItem. For that you can choose to maintain states in the cell as:
func changeToSelectedState() {
self.backgroundColor = UIColor.white
}
func changeToUnselectedState() {
self.backgroundColor = UIColor.clear
}
Or you can choose to write the same in the controller itself. And then you need to perform the deselection and selection in the following manner.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
let previousSelectedCell = collectionView.cellForItem(at: selectedIndexPath)
previousSelectedCell.changeToUnselectedState()
let currentCell = collectionView.cellForItem(at: indexPath)
currentCell.changeToSelectedState()
selectedIndexPath = indexPath
}
please bear with me as I am new to swift programming.
I have a myCollectionViewController that is a subclass of UICollectionViewController. The cells for the MyCollectionViewController are a class of MyCollectionViewCell, which is a custom UICollectionViewCell.
What I am trying to do is change the background of the MyCollectionViewCell based on the user selection AND have this selection persist when the user scrolls to other cells of the MyCollectionViewController. I have tried two ways to do this, and so far both have failed.
The first way was to write code in the didSelectItemAt method of the MyCollectionViewController:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! MyCollectionViewCell
cell.contentView.backgroundColor = UIColor.red
}
However, this did not work and the cell colour was not changed.
The other way I tried to do this was by changing the isSelected property of the MyCollectionViewCell.
override var isSelected: Bool {
// Change what happens when the user selects a cell
didSet {
if self.isSelected {
self.contentView.backgroundColor = Colours.primary
} else {
self.contentView.backgroundColor = Colours.secondary
}
}
}
Although this worked, the selection did not persist. That is when the user scrolled to a different cell in the collectionView and then scrolled back, the selection was gone.
Any advice would be appreciated.
Don't use dequeue in didSelectItemAt as it'll return other cell than the clicked
var allInde = [IndexPath]()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at:indexPath) as! MyCollectionViewCell
cell.contentView.backgroundColor = UIColor.red
if !(allIndex.contains(indexPath)) {
allInde.append(indexPath)
}
}
and in cellForItem check whether indexpath to show is in the array and color it
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "id", for: indexPath as IndexPath) as! MyCollectionViewCell
if allIndex.contains(indexPath) {
cell.contentView.backgroundColor = Colours.primary
}
else {
cell.contentView.backgroundColor = Colours.secondary
}
}
// se here updated code
SPRAIN
Im using UIcollection view as my tabbar
when I scroll collection view horizontally previous selected cell will not deselect when i select new one
this is my code to change colour when i select a cell and deselect a cell
var selectedIndexPath : IndexPath = []
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as?
BottomCollectionViewCell {
cell.contentView.backgroundColor = UIColor.orange
cell.backgroundColor = UIColor.orange
}
if let preViousSelectedcell = collectionView.cellForItem(at:
selectedIndexPath) as? BottomCollectionViewCell {
preViousSelectedcell.contentView.backgroundColor=UIColor.purple
preViousSelectedcell.backgroundColor = UIColor.purple
}
selectedIndexPath = indexPath
}
while scrolling cells are reused that time cellForItemAt will call so you need to change some modification in your code
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
selectedIndexPath = indexPath
YOUR_COLLECTION_VIEW.reloadData()
}
and add below lines inside your collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)
if indexPath == selectedIndexPath {
cell.contentView.backgroundColor=UIColor.purple
cell.backgroundColor = UIColor.purple
} else {
cell.contentView.backgroundColor = UIColor.orange
cell.backgroundColor = UIColor.orange
}
Hope this will help you
I have two CollectionView's. One CollectionView (allHobbiesCV) is pre-populated with Hobbies you can select. The other CollectionView (myHobbiesCV) is empty, but if you tap on a hobby in the allHobbiesCV, it gets added to the myHobbiesCV. This is all working great.
I would like the tapped allHobbiesCV cells to switch to selected, it adds the hobby to myHobbiesCV, then if the user taps the same selected cell again in the allHobbiesCV, it removes that hobby from the myHobbiesCV. Basically a toggle add/remove.
Two things to note:
Users can manually select hobbies in myHobbiesCV, then tap a [Remove Hobby] button.
Hobbies will be sorted by seasons, so there will be 4 different data sets (Winter, Spring, Summer, Autumn) for allHobbiesArray. Depending on which season (ViewController) the user taps. They can select as many / few cells from each as they like.
Problem:
I'm crashing on any toggle besides the first cell. If I select the first cell in allHobbiesCV, I can select it again, and it will remove it from the myHobbiesCV. If I select that same cell again (to toggle it,) I crash. If I select any other cell besides the first, I crash.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete item 7 from section 0 which only contains 3 items before the update'
Class Level
// Winter Hobbies
let allHobbiesArray = ["Skiing", "Snowboarding", "Drinking Bourbon", "Snow Shoeing", "Snowmobiling", "Sledding", "Shoveling Snow", "Ice Skating"]
var myHobbiesArray = [String]()
var allSelected = [IndexPath]()
var didSelectIPArray = [IndexPath]()
Data Source
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == allHobbiesCV {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ALL", for: indexPath) as! AllHobbiesCell
cell.allHobbiesLabel.text = allHobbiesArray[indexPath.item]
if cell.isSelected {
cell.backgroundColor = UIColor.green
}
else {
cell.backgroundColor = UIColor.yellow
}
return cell
}
else {
let cell = myHobbiesCV.dequeueReusableCell(withReuseIdentifier: "MY", for: indexPath) as! MyHobbiesCell
cell.myHobbiesLabel.text = myHobbiesArray[indexPath.item]
if cell.isSelected {
cell.backgroundColor = UIColor.red
}
else {
cell.backgroundColor = UIColor.yellow
}
return cell
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == allHobbiesCV {
return allHobbiesArray.count
}
else {
return myHobbiesArray.count
}
}
}
Delegate
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == allHobbiesCV {
if myHobbiesArray.count <= 6
self.allSelected = []
didSelectIPArray.append(indexPath) // Store the selected indexPath in didSelectIPArray
myHobbiesArray.insert(allHobbiesArray[indexPath.item], at: 0)
let allHobbiesCell = allHobbiesCV.cellForItem(at: indexPath) as! AllHobbiesCell
allHobbiesCell.backgroundColor = UIColor.green
// Store all of the selected cells in an array
didSelectIPArray.append(indexPath)
myHobbiesCV.reloadData()
}
}
else {
let cell = myHobbiesCV.cellForItem(at: indexPath) as! MyHobbiesCell
cell.backgroundColor = UIColor.red
allSelected = self.myHobbiesCV.indexPathsForSelectedItems!
}
}
// Deselecting selected cells
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if collectionView == allHobbiesCV {
// Yellow is the unselected cell color. So let's change it back to yellow.
let allHobbiesCell = allHobbiesCV.cellForItem(at: indexPath) as! AllHobbiesCell
allHobbiesCell.backgroundColor = UIColor.yellow
// Remove (toggle) the cells. This is where I am stuck/crashing.
let indices = didSelectIPArray.map{ $0.item }
myHobbiesArray = myHobbiesArray.enumerated().flatMap { indices.contains($0.0) ? nil : $0.1 }
self.myHobbiesCV.deleteItems(at: didSelectIPArray)
myHobbiesCV.reloadData()
didSelectIPArray.remove(at: indexPath.item) // Remove the deselected indexPath from didSelectIPArray
}
else { // MyHobbies CV
let cell = myHobbiesCV.cellForItem(at: indexPath) as! MyHobbiesCell
cell.backgroundColor = UIColor.yellow
// Store the selected cells to be manually deleted.
allSelected = self.myHobbiesCV.indexPathsForSelectedItems!
}
}
}
Delete Button
#IBAction func deleteButtonPressed(_ sender: UIButton) {
for item in didSelectIPArray {
self.allHobbiesCV.deselectItem(at: item, animated: false) // Try deselecting the deleted items in allHobbiesCV... This is crashing.
}
}
The issue is how I am trying to toggle the allHobbiesCV cells in didDeselect. Was my approach correct in saving the selected cells in didSelectIPArray?
I'll be happy to provide further insight if needed. Thank you much in advance friends!
There is no need for all of that map and flatmap stuff. You can simply remove the object from the selected hobbies array:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == allHobbiesCV {
if myHobbiesArray.count <= 6
self.allSelected = []
myHobbiesArray.insert(allHobbiesArray[indexPath.item], at: 0)
let allHobbiesCell = allHobbiesCV.cellForItem(at: indexPath) as! AllHobbiesCell
allHobbiesCell.backgroundColor = UIColor.green
myHobbiesCV.insertItems(at: [IndexPath(item: 0, section: 0)])
}
} else {
let cell = myHobbiesCV.cellForItem(at: indexPath) as! MyHobbiesCell
cell.backgroundColor = UIColor.red
allSelected = self.myHobbiesCV.indexPathsForSelectedItems!
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if collectionView == allHobbiesCV {
// Yellow is the unselected cell color. So let's change it back to yellow.
let allHobbiesCell = allHobbiesCV.cellForItem(at: indexPath) as! AllHobbiesCell
allHobbiesCell.backgroundColor = UIColor.yellow
let hobby = allHobbiesArray[indexPath.item]
if let index = myHobbiesArray.index(of:hobby) {
myHobbiesArray.remove(at: index)
myHobbiesCV.deleteItems(at: [IndexPath(item: index, section:0)])
}
} else { // MyHobbies CV
let cell = myHobbiesCV.cellForItem(at: indexPath) as! MyHobbiesCell
cell.backgroundColor = UIColor.yellow
// Store the selected cells to be manually deleted.
allSelected = self.myHobbiesCV.indexPathsForSelectedItems!
}
}