动态向UITableView单元格添加视图出现问题,求排查解决
嘿,我一眼就看出你这儿的问题了——是UITableView的单元格复用机制在搞鬼!每次滚动列表时,那些滚出屏幕的cell会被放进复用池,下次需要显示新行时又被取出来重新使用。而你现在的代码每次都会给cell的contentView加一个新的UILabel,时间一长,一个cell里会叠上好几个label,自然就会出现文字重叠、布局混乱的问题。
咱们来拆解下问题所在:你的cellForRowAt方法每次创建新Label并添加,但完全没考虑cell已经被复用、里面已经有旧Label的情况,重复添加就导致了异常。
下面给你几个靠谱的解决方案,从临时修复到规范实现都有:
方案1:用固定Tag复用已有Label(快速修复)
你之前给Label设了tag = indexPath.row,这其实不对——因为cell复用后,indexPath.row会变,tag跟着变就没法准确找到已存在的Label。咱们换个固定的tag值,比如1001,然后每次先查找有没有这个tag的Label,有就直接更新文本,没有再创建:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)! // 先找已存在的Label if let existingLabel = cell.contentView.viewWithTag(1001) as? UILabel { existingLabel.text = "\(data[indexPath.row])" } else { // 没有才创建新的 let label = UILabel(frame: CGRect(x: 100, y: 14, width: 400, height: 30)) label.text = "\(data[indexPath.row])" label.tag = 1001 // 用固定不变的tag cell.contentView.addSubview(label) } return cell }
方案2:自定义UITableViewCell(推荐,长期维护更省心)
这是iOS开发里处理自定义cell的标准做法,把需要的视图提前定义在自定义cell类里,只添加一次,后续只用更新内容就行,完全避开复用问题。
步骤1:创建自定义Cell类
class CustomTableViewCell: UITableViewCell { // 提前初始化好需要的Label let customLabel: UILabel = { let label = UILabel() // 如果你用frame的话可以这么设,不过更推荐AutoLayout适配不同屏幕 label.frame = CGRect(x: 100, y: 14, width: 400, height: 30) return label }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) // 只在cell初始化时添加一次Label到contentView contentView.addSubview(customLabel) // 【可选】换成AutoLayout,适配不同尺寸的设备(推荐) // customLabel.translatesAutoresizingMaskIntoConstraints = false // NSLayoutConstraint.activate([ // customLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 100), // customLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), // customLabel.widthAnchor.constraint(equalToConstant: 400), // customLabel.heightAnchor.constraint(equalToConstant: 30) // ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
步骤2:注册自定义Cell
如果是用代码创建的tableView,记得提前注册这个自定义cell:
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: cellIdentifier)
如果是用Storyboard/XIB,直接在界面里设置cell的类为CustomTableViewCell就行。
步骤3:修改cellForRowAt方法
现在就简单多了,直接取出自定义cell,更新Label文本:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! CustomTableViewCell cell.customLabel.text = "\(data[indexPath.row])" return cell }
方案3:清空所有子视图(不推荐,临时救急可用)
如果只是想快速测试效果,可以在添加新视图前清空cell的所有子视图,但这样会影响性能,而且如果cell原本有默认的textLabel、detailTextLabel也会被删掉,所以只适合临时用:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)! // 清空contentView下所有子视图 cell.contentView.subviews.forEach { $0.removeFromSuperview() } let label = UILabel(frame: CGRect(x: 100, y: 14, width: 400, height: 30)) label.text = "\(data[indexPath.row])" cell.contentView.addSubview(label) return cell }
另外,你提到列数是动态的,如果需要添加多个视图,推荐用UIStackView来管理——把动态生成的视图加到栈视图里,这样布局更灵活,也方便后续调整。
内容的提问来源于stack exchange,提问作者Benjamin RD

