iosuitableviewuistackviewreuseidentifier

Add UIViews to UIStackView programmatically inside UITableView


I have three elements UILabel, UIImageView and UIButton in a list. I have to show them accordingly inside UITableView. I have an array like this:

tableArray = [["label", "img", "button"],["img","button","label"],["img","label","button"],["button", "img", "label"]]

The positions of the elements are same as the positions of them (index) inside the array. My cellForRowAt looks like this:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "FirstTableViewCell", for: indexPath) as? FirstTableViewCell else {
        return UITableViewCell()
    }

    let tableData = tableArray[indexPath.row]

    var count = 0
    tableData.forEach { (element) in
        switch element {
        case "label":
            let lbl = self.createLabel()

            cell.stackView.insertArrangedSubview(lbl, at: count)
            count += 1
            break
        case "img":
            let img = self.createImage()
            cell.stackView.insertArrangedSubview(img, at: count)
            count += 1
            break
        case "button":
            let btn = self.createButton()
            cell.stackView.insertArrangedSubview(btn, at: count)
            count += 1
            break
        default:
            break
        }
    }
    return cell
}

The problem is whenever I am scrolling TableView every time those items are added into the cells.

I have tried few solutions to solve that but no luck.

  1. if tableView.cellForRow(at: indexPath) == nil then add elements into stackviews.

  2. Checking if cell == nil after dequeueReusableCell. If nil then init the cell.

  3. let cell = UITableViewCell.init(style: .default, reuseIdentifier: nil) as? FirstTableViewCell tried even without dequeueReusableCell.

But the same thing happens every time.

This is the FirstTableViewCell

class FirstTableViewCell: UITableViewCell {

    @IBOutlet weak var stackView: UIStackView!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
}

Any idea how to check if elements are already added to StackView, then I'll not add them.


Solution

  • Because the cells are reused, the first thing you need to do in cellForRowAt is "reset" your cell... in other words, clear out the existing subviews from the stack view:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "FirstTableViewCell", for: indexPath) as? FirstTableViewCell else {
            return UITableViewCell()
        }
    
        // remove the views that are currently in the stack
        cell.stackView.arrangedSubviews.forEach {
            $0.removeFromSuperview()
        }
    
        // the rest of your setup
        let tableData = tableArray[indexPath.row]
    
        var count = 0
        tableData.forEach { (element) in
        ...
    }
    

    Now, based on the code you presented, if the cells always contain a UILabel, UIImageView and UIButton, just in different orders and with different properties, you could add them once when you create the cell (or add them in your cell prototype if you're using one), and then simply re-arrange them as needed.