3

I am having trouble finding a solution for this issue. I am using UISwitch inside UICollectionViewCell and I am passing a boolean variable to set switch on or off.

The condition is only one switch has to be ON at a time from all cells. But When I turn one switch on another random switch's tint color changes that means its state changed.

By default switch status is ON in storyboard and even if I set it OFF nothing changes.

I couldn't figure out why is this happening.

Here is my code for cellForItemAtIndexPath

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AddEditItemPopupView.cellId, for: indexPath) as! DiscountCollectionViewCell
        cell.delegate = self

        let currentDiscount = allDiscounts[indexPath.item]
        let shouldApplyDiscount = updatedDiscountId == currentDiscount.id
        cell.updateCellWith(data: currentDiscount, applyDiscount: shouldApplyDiscount)
        return cell
    }

And code for cell class

func updateCellWith(data: DiscountModel, applyDiscount: Bool) {
        let name = data.title.replacingOccurrences(of: "Discount ", with: "")
        self.titleLabel.text = String(format: "%@ (%.2f%%)", name, data.value)
        self.switchApply.isOn = applyDiscount
        self.switchApply.tag = data.id
    }

Data source contains objects of DiscountModel which look like this:

{
    id: Int!
    title: String!
    value: Double!
}

Switch value changed method inside cell class:

@IBAction func switchValueChanged(_ sender: UISwitch) {
        if sender.isOn {
            self.delegate?.switchValueDidChangeAt(index: sender.tag)
        }
        else{
            self.delegate?.switchValueDidChangeAt(index: 0)
        }
    }

Delegate method inside view controller class:

func switchValueDidChangeAt(index: Int) {
        self.updatedDiscountId = index
        self.discountCollectionView.reloadData()
    }
Paras Gorasiya
  • 1,295
  • 2
  • 13
  • 33
  • If this is happening without scrolling, it might not be cell reuse. Is it a static or dynamic collection view? – Chris Jan 26 '19 at 07:43
  • Also, what is switchApply? – Chris Jan 26 '19 at 07:45
  • @Chris switchApply is an outlet of UISwitch from collectionViewCell. Its a dynamic collection view and currently contains only 4 cells which fits within the width of collectionView and yes this is happening without scrolling. So what could cause this issue? – Paras Gorasiya Jan 26 '19 at 07:49
  • I feel like these two lines might be the problem (but not sure): `let currentDiscount = ...` and `let shouldApplyDiscount = ...` – Chris Jan 26 '19 at 07:56
  • Nope, I debugged and verified that shouldApplyDiscount has correct value for each index every time when switch status is changed. – Paras Gorasiya Jan 26 '19 at 08:00
  • You could have an integer variable in the collection view delegate view controller that is changed and changed in the `cellForRow` method depending on the switch. This would mean only one could be activated. – Chris Jan 26 '19 at 08:01
  • Cool, thanks for info – Chris Jan 26 '19 at 08:02
  • @Chris Sorry couldn't understand what you are trying to explain. – Paras Gorasiya Jan 26 '19 at 08:06
  • @Sulthan If I dont use tags how would I be able to identify which switch's value changed? – Paras Gorasiya Jan 26 '19 at 08:08
  • Show us the delegate that handles clicks. Show how you are using tags. I am also worried about the color change. It seems the tint color is changing for some reason too. Are you setting that in code somewhere? – Sulthan Jan 26 '19 at 08:08
  • @Sulthan Please have a look at updated question and I am not setting tint color in code, all those are defaults in storyboard. – Paras Gorasiya Jan 26 '19 at 08:11
  • Could you try to change `.isOn = applyDiscount` to `self.switchApply.setOn(applyDiscount, animated: false)`? – Sulthan Jan 26 '19 at 08:14
  • @Sulthan Tried already, nothing changes. – Paras Gorasiya Jan 26 '19 at 08:17
  • 1
    What you are doing is somewhat problematic. When you click the switch, it starts an animation but you immediately reload the cell, resetting `isOn`, therefore cancelling the animation. I would try either `if switchApply.isOn != applyDiscount { switchApply.isOn = applyDiscount }` or `DispatchQueue.main.async { self.switchApply.isOn = applyDiscount }`. A possible solution is also to disable interaction on the switch and handle clicks (selection) on the whole cell instead. – Sulthan Jan 26 '19 at 08:22
  • @Sulthan It works if I handle clicks on cell. – Paras Gorasiya Jan 26 '19 at 08:43

1 Answers1

2

There are a few improvements I would suggest to your code;

  • Reloading the entire collection view is a bit of a shotgun
  • Since it is possible for there to be no discount applied, you should probably use an optional for your selected discount, rather than "0"
  • Using Tag is often problematic

I would use something like:

var currentDiscount: DiscountModel? = nil

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AddEditItemPopupView.cellId, for: indexPath) as! DiscountCollectionViewCell
    cell.delegate = self

    let item = allDiscounts[indexPath.item]
    self.configure(cell, forItem: item)

    return cell
}

func configure(_ cell: DiscountCollectionViewCell, forItem item: DiscountModel) {
    cell.switchApply.isOn = false
    let name = item.title.replacingOccurrences(of: "Discount ", with: "")
    self.titleLabel.text = String(format: "%@ (%.2f%%)", name, item.value)

    guard let selectedDiscount = self.currentDiscount else {
        return
    }

    cell.switchApply.isOn = selectedDiscount.id == item.id
}

func switchValueDidChangeIn(cell: DiscountCollectionViewCell, to value: Bool) {
    if value {
        if let indexPath = collectionView.indexPath(for: cell) {
           self.currentDiscount = self.allDiscounts[indexPath.item]
        }
    } else {
        self.currentDiscount = nil
    }
    for indexPath in collectionView.indexPathsForVisibleItems {
        if let cell = collectionView.cellForItem(at: indexPath) {
            self.configure(cell, forItem: self.allDiscounts[indexPath.item])
        }
    }
}

In your cell:

@IBAction func switchValueChanged(_ sender: UISwitch) {
    self.delegate?.switchValueDidChangeIn(cell:self, to: sender.isOn)
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186