0

I have a typical UITableView with custom cells. All cells are of the same type - one UIStackView which contains the views of the same type but varies their count.

Tried to use one cell for all - it is slow to delete-add inner views.

Tried to create different cells for different count of subviews:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let row = rows[indexPath.row]
        let CellIdentifier = "CellIdentifier\(row.subrows.count)"
        var cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as? CustomCell
        if cell == nil {
            tableView.register(UINib(nibName: "WTRemindersTableViewCell", bundle: nil), forCellReuseIdentifier: CellIdentifier)
            cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as? CustomCell
            for _ in row.subrows {
                let v = Bundle.main.loadNibNamed("CustomInnerView", owner: self, options: nil)?.first as! WTRemindersCellLineView
                cell!.stackView.addArrangedSubview(v)
            }
        }
        return cell!
    }

but it seems it registers just a cell without of subviews and it has no sense to use different cell identifiers (because each time you need to add stackview subviews manually).

How to solve this issue?

Edited

Added code for cell

class CustomCell: UITableViewCell {
    @IBOutlet var boxView: UIView!
    @IBOutlet var imgView: UIImageView!
    @IBOutlet var lblTitle: UILabel!
    @IBOutlet var lblSubtitle: UILabel!
    @IBOutlet var dashedView: WTDashedView!
    @IBOutlet var stackView: UIStackView!
    
    weak var delegate: CustomCellDelegate?
    var index: Int!
    
    lazy var onceDraw: () = {
        boxView.layer.applySketchShadow()
    }()
    
    override func awakeFromNib() {
        super.awakeFromNib()
        boxView.layer.cornerRadius = 19
        imgView.layer.cornerRadius = 12
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        _=onceDraw
    }
    
    @IBAction func btnDotsClicked(_ sender: Any) {
        delegate?.btnDotsClicked(at: index)
    }
}
Vyachaslav Gerchicov
  • 2,317
  • 3
  • 23
  • 49
  • This is a really bad approach in a couple ways. You should never (well, never say never, but) add subviews to cells in `cellForRowAt` -- you should have all that logic in your cell class and tell your cell to configure itself. You also shouldn't be registering cell classes inside `cellForRowAt`. It would help to know what's in your "CustomInnerView" to give you better advice. – DonMag Aug 14 '20 at 12:58
  • @DonMag `CustomInnerView` contains just a plain set of images, labels and a button. Inner elements are linked with each other using simple constraints. I agree that it is maybe a bad approach so I asked if there is another sollution which is better – Vyachaslav Gerchicov Aug 14 '20 at 13:03
  • Edit your question and include the code for your `CustomCell` class. – DonMag Aug 14 '20 at 13:11
  • @DonMag ok, added code for my cell. There is nothing especial – Vyachaslav Gerchicov Aug 14 '20 at 13:16
  • Is your data going to have a limited number of "subrows? That is, might you have 100 rows, but each will have a maximum of, say, 5 "subrows"? Or is it possible some may have 20 subrows? – DonMag Aug 14 '20 at 14:43
  • @DonMag currently it is unlimited. But I hope the max value won't be greater than 64. – Vyachaslav Gerchicov Aug 17 '20 at 07:43
  • hmmm... so you could have 64 reuse identifiers... or potentially more? This sounds like it would work much better to make your "custom inner view" a separate cell class. – DonMag Aug 17 '20 at 14:25
  • @DonMag it will be a hell to create all 64 cells as separate classes. And note - some of them may never be used to register them in table and/or keep them in memory – Vyachaslav Gerchicov Aug 18 '20 at 09:59
  • No, you would only have two cell classes... one with the “top” elements from your current cell, the other would be essentially your custom inner view. So you fill the table with “A B B B A B B B B B B A B B A B B B ...” Or, use the top elements as a section header view, and each section of your table contains your 1-64 (or more) rows of inner views. – DonMag Aug 18 '20 at 11:25

1 Answers1

0

First, you should register your cell in viewDidLoad. Note I'm not sure what your nib is named, so make sure to adjust it for your needs.

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.register(
        UINib(nibName: "CustomCell", bundle: nil),
        forCellReuseIdentifier: "CustomCell"
    )
}

It's hard to tell what you were doing in your cellForRowAt method but this will do the trick. Cells are reused so make sure that you remove any remaining arranged subviews. You shouldn't have any performance issues if you do it this way:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell

    let row = rows[indexPath.row]

    cell.stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
    for _ in row.subrows {
        cell.stackView.addArrangedSubview(cellView())
    }

    return cell
}

This is just a little helper method to instantiate your view:

func cellView() -> WTRemindersCellLineView {
    return Bundle.main.loadNibNamed(
        "WTRemindersCellLineView", owner: nil, options: nil
        )?.first as! WTRemindersCellLineView
}
Rob C
  • 4,877
  • 1
  • 11
  • 24
  • I found a better solution which adds and removes subviews when it is necessary only. It is possible because all these subviews are of the same type (their count differs only). And I use different cell identifiers for various subview count - it doesn't allow to avoid extra add/remove subview actions but at least significantly reduces their count. About your `cellView()` - you should mention that it works for a single cell identifier only. – Vyachaslav Gerchicov Aug 14 '20 at 10:43