Search code examples
swiftconstraintsuicollectionviewcellbackground-color

Unable to change collectionview cell background colour in Swift


I have given constraints for image and label like this

Image constraints. Label Constraints

code: with this code i am able to get selectedNamesArray but unable to change cell colour. where am i wrong? is there anything wrong in storyboard constraints so that cell BG is not changing? or any other coding mistake? could anyone please explain the issue and the solution for cell background change

class TestCollectionCell: UICollectionViewCell{
@IBOutlet weak var roundImg: UIImageView!
@IBOutlet weak var titleLbl: UILabel!

override func awakeFromNib() {
    super.awakeFromNib()
}

override func layoutSubviews() {
    super.layoutSubviews()
    roundImg.layer.cornerRadius = roundImg.frame.width / 2
    roundImg.contentMode = .scaleToFill
    roundImg.clipsToBounds = true
}
}


override func viewDidLoad() {
super.viewDidLoad()

collectionView.delegate = self
collectionView.dataSource = self

let layOut = UICollectionViewFlowLayout()
layOut.scrollDirection = .vertical

collectionView.collectionViewLayout = layOut
collectionView.allowsMultipleSelection = true
GenericCodableServicecall()
}

var cvWidth: CGFloat = -1.0

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if cvWidth != collectionView.frame.size.width {
    cvWidth = collectionView.frame.size.width
    if let fl = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {

        let nColumns: Int = 4
        
        let sideSpace: CGFloat = fl.sectionInset.left + fl.sectionInset.right
        
        let totalEmptyspace: CGFloat = sideSpace + (CGFloat(nColumns) * 3.0)
        
        let w = (cvWidth - totalEmptyspace) / CGFloat(nColumns)
        fl.itemSize = .init(width: w, height: w)

        fl.minimumInteritemSpacing = 0.0
    }
}
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
namesArray.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TestCollectionCell", for: indexPath) as! TestCollectionCell
cell.layoutIfNeeded()
cell.titleLbl.text = namesArray[indexPath.row]
return cell
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)     {

print("in select..")

let cell = collectionView.cellForItem(at: indexPath) as! TestCollectionCell
let selectedName = namesArray[indexPath.row]
if let inx = selectedNamesArray.firstIndex(of: selectedName){
    selectedNamesArray.remove(at: inx)
    cell.backgroundColor = UIColor.red
}
else{
    selectedNamesArray.append(selectedName)
    cell.backgroundColor = UIColor.yellow
}
print(selectedNamesArray)
}

unable to change cell background when i select/deselect always showing green background only.

EDIT:

// MARK: - TestAPIModel
struct TestAPIModel: Codable {
let page, perPage, total, totalPages: Int
var data: [Datum]
let support: Support

enum CodingKeys: String, CodingKey {
    case page
    case perPage = "per_page"
    case total
    case totalPages = "total_pages"
    case data, support
}
}

// MARK: - Datum
struct Datum: Codable {
let id: Int
let name: String
let year: Int
let color, pantoneValue: String
var isSelected: Bool = false
enum CodingKeys: String, CodingKey {
    case id, name, year, color
    case pantoneValue = "pantone_value"
    case isSelected = "is_selected"

}
}

after changing model like above o/p:

decodingFailed(Alamofire.AFError.responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "is_selected", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: "is_selected", intValue: nil) ("is_selected").", underlyingError: nil)))))

this is my serviceCAll case here how to check nil data please guide

var jsonData: TestAPIModel? { didSet{ print(jsonData?.data) }

}

NetworkService.request("https://reqres.in/api/unknown", method: .get, parameters: parameters) { (result: Result<TestAPIModel, APIError>) in
        switch result {
        case .success(let user):
            print("response \(user)")
            self.jsonData = user
            self.collectionView.reloadData()

        case .failure(let error):
            print("Error: \(error)")
        }
    }

Solution

  • An extra array for the selected state is very bad practice. It's cumbersome to maintain and you have to set the color accordingly in cellForItemAt because cells are reused when the user scrolls.

    A better way is a struct for the data source with a member isSelected

    struct Person {
        let name: String
        var isSelected = false
    }
    

    Then the data source array is

    var people = [Person]()
    

    and the data source and methods are

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        people.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TestCollectionCell", for: indexPath) as! TestCollectionCell
        cell.layoutIfNeeded()
        let person = people[indexPath.row]
        cell.titleLbl.text = person.name
        cell.backgroundColor = person.isSelected ? .red : .yellow
        return cell
    }
    

    In didSelect just toggle isSelected of the item at given indexPath and reload the item

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)     {
    
        print("in select..")
    
        people[indexPath.row].isSelected.toggle()
        collectionView.reloadItems(at: [indexPath])
    }