I have a collection view that looks to a string array.
I'm looking to solve these questions related to the color of the View:
On Load, I'd like the cell "All Venues" to be highlighted blue/white font while the remaining cells to be their default white with black text.
Upon click of an additional cell, I'd like all cells including "All Venues" to change to default and the cell selected to be blue/white. If a cell has already been selected previously and I choose another cell, I'd like this previous to default.
Here is my code:
//Variables
var selectedItems: [String] = []
var selectedItem = String()
var previousIndex = 0
//Collection View Functions
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! FilterCollectionViewCell
let newSelectedItem = collectionViewArray[indexPath.row].name ?? ""
let previousIndexPath = IndexPath(index: previousIndex)
if selectedItem == newSelectedItem {
//if the same item, nothing happens
} else if selectedItem != newSelectedItem && cell.isHighlightedCell == false {
if previousIndex != nil {
//this is what I tried, if the item has already been selected, I try to reference that previous cell with that cell's indexPath
let previousCell = collectionView.cellForItem(at: previousIndexPath) as! FilterCollectionViewCell
previousCell.isHighlightedCell = false
previousCell.filterContainerView.backgroundColor = .none
previousCell.nameLabel.textColor = .black
}
//Changes are Made
cell.isHighlightedCell = true
selectedItems.append(selectedItem ?? "")
cell.filterContainerView.backgroundColor = UIColor(hexString: "001A38")
cell.nameLabel.textColor = .white
}
if newSelectedItem != "All Venues" {
searchIsActive = true
selectedItems.removeAll()
} else {
searchIsActive = false
}
//changes Selection
selectedItem = newSelectedItem
previousIndex = indexPath.row
//reload/animation
self.barsShown = barArray.filter{ $0.type == selectedItem}
barDisplayedTableView.reloadData()
updateAnimationForTableView()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterCollectionViewCell", for: indexPath) as! FilterCollectionViewCell
//Tried to implement this for All Venues as default
if indexPath.row == 0 && indexPath.row == previousIndex {
cell.filterContainerView.backgroundColor = UIColor(hexString: "001A38")
cell.nameLabel.textColor = .white
}
//if true, it displays this color
if cell.isHighlightedCell == true {
cell.filterContainerView.backgroundColor = UIColor(hexString: "001A38")
cell.nameLabel.textColor = .white
} else {
cell.filterContainerView.backgroundColor = .none
cell.nameLabel.textColor = .black
}
return cell
}
CodePudding user response:
You can save yourself a lot of work by letting the cell class handle its own selected / unselected appearance.
So, if we have a simple cell class with a label:
class LabelCollectionViewCell: UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
])
// default (unselected) appearance
contentView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
label.textColor = .black
// let's round the corners so it looks nice
contentView.layer.cornerRadius = 12
}
}
and we want this difference between selected / unselected:
we can override isSelected
in that cell class:
override var isSelected: Bool {
didSet {
contentView.backgroundColor = isSelected ? .systemBlue : UIColor(white: 0.95, alpha: 1.0)
label.textColor = isSelected ? .white : .black
}
}
Now, the color changes - including changing the currently selected cell back to the "default" colors - is fully automatic.
And, because the collection view manages the selection state internally, we don't even need to do anything in cellForItemAt
... even if we've scrolled the selected cell out-of-view.
Here's a quick, complete example:
class LabelCollectionViewCell: UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
])
// default (unselected) appearance
contentView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
label.textColor = .black
// let's round the corners so it looks nice
contentView.layer.cornerRadius = 12
}
override var isSelected: Bool {
didSet {
contentView.backgroundColor = isSelected ? .systemBlue : UIColor(white: 0.95, alpha: 1.0)
label.textColor = isSelected ? .white : .black
}
}
}
class SelColViewVC: UIViewController {
var collectionView: UICollectionView!
let myData: [String] = [
"All Venues",
"Venue One",
"Venue Two",
"Venue Three",
"Venue Four",
"Venue Five",
"Venue Six",
"Venue Seven",
"Venue Eight",
]
// use this to track the currently selected item / cell
var currentSelection: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
let fl = UICollectionViewFlowLayout()
fl.scrollDirection = .horizontal
fl.estimatedItemSize = CGSize(width: 120, height: 50)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
collectionView.heightAnchor.constraint(equalToConstant: 80.0),
])
collectionView.contentInset = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0)
collectionView.register(LabelCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionView.dataSource = self
collectionView.delegate = self
// we want to "pre-select" the first item / cell
let idx = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: idx, animated: false, scrollPosition: .left)
// update currentSelection var
currentSelection = idx
}
}
extension SelColViewVC: UICollectionViewDataSource, UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! LabelCollectionViewCell
c.label.text = myData[indexPath.item]
return c
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if currentSelection == indexPath {
// user tapped already selected cell, so
// just return
print("Tapped Already Selected item:", indexPath.item)
return
}
// update currentSelection var
currentSelection = indexPath
print("New Selected item:", indexPath.item)
// run code for new selection
}
}