Swift UITableViewCell复用后约束设置失效问题求助
问题回顾
你遇到的这个UITableViewCell复用坑我太熟悉了!自定义Cell里的titleIndentationConstraint需要根据data.hasWeblink切换constant:有链接时设为weblink的高度,没链接时设为0。初次显示一切正常,但滚动后部分复用Cell的约束值没更新,其他元素都正常,加了setNeedsLayout()和layoutIfNeeded()也没用。
先把你的相关代码贴出来方便分析:
TableViewController 的 cellForRowAt 方法
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier = whichCellToShow ? "ThisCell" : "TheOtherCellCell" //... let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) if let customCell = cell as? CustomCellProtocol, indexPath.section < data.count, indexPath.row < data[indexPath.section].count { customCell.display(data: data[indexPath.section][indexPath.row]) } return cell }
自定义Cell代码
class CustomCell: UITableViewCell, CustomCellProtocol { @IBOutlet weak var picture: UIImageView! @IBOutlet weak var title: UILabel! @IBOutlet weak var weblink: UILabel! @IBOutlet weak var titleIndentationConstraint: NSLayoutConstraint! //... func display(data: Data) { //... title.text = data.title //... weblink.text = data.hasWeblink ? URLUtilities.hostOfURL(urlstr: data.sourceUrl) : nil weblink.isHidden = !data.hasWeblink //问题疑似出在此处 titleIndentationConstraint.constant = data.hasWeblink ? weblink.frame.height : 0 setNeedsLayout() layoutIfNeeded() } }
问题核心原因
问题就出在获取weblink.frame.height的时机不对!
当你在display方法里刚设置完weblink的文本和隐藏状态,立刻去取它的frame.height,此时Auto Layout还没来得及根据新数据重新计算布局——复用的Cell里weblink的frame还是上一次复用的旧值,根本不是当前数据对应的真实高度。
你加的setNeedsLayout()和layoutIfNeeded()之所以没用,是因为布局计算还没基于新数据完成,强制布局也还是用的旧尺寸。
解决方案
给你两个实用的方案,选一个适合你的:
方案一:让Auto Layout自动处理(推荐,少写代码少踩坑)
与其手动计算高度,不如把布局逻辑完全交给Auto Layout:
- 给
weblink添加一个高度约束,将其优先级设为999(低于默认的1000,这样当weblink隐藏时可以被压缩)。 - 把
titleIndentationConstraint替换成垂直间距约束——让title的顶部和weblink的底部直接相连。 - 在
display方法里只需要处理weblink的文本和状态:
func display(data: Data) { //... title.text = data.title //... if data.hasWeblink { weblink.text = URLUtilities.hostOfURL(urlstr: data.sourceUrl) weblink.isHidden = false // 让weblink自适应高度 weblinkHeightConstraint.constant = UITableView.automaticDimension } else { weblink.text = nil weblink.isHidden = true weblinkHeightConstraint.constant = 0 } }
这样Auto Layout会自动调整title的位置,完全不需要手动设置constant,复用的时候也不会有旧值残留的问题。
方案二:正确获取weblink的真实高度
如果你一定要手动设置constant,那得确保拿到的是当前数据对应的真实高度:
func display(data: Data) { //... title.text = data.title //... weblink.isHidden = !data.hasWeblink if data.hasWeblink { weblink.text = URLUtilities.hostOfURL(urlstr: data.sourceUrl) // 先让weblink计算出正确的尺寸 weblink.sizeToFit() // 或者用systemLayoutSizeFitting获取更准确的Auto Layout高度 let actualHeight = weblink.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height titleIndentationConstraint.constant = actualHeight } else { weblink.text = nil titleIndentationConstraint.constant = 0 } // 这里不需要手动调用setNeedsLayout和layoutIfNeeded,UITableView会自动处理布局 }
用sizeToFit()或者systemLayoutSizeFitting()让weblink先计算出自己的真实高度,再赋值给约束,这样就不会用到旧的frame值了。
额外提醒
- 检查你的Cell有没有在
awakeFromNib或者layoutSubviews里修改这个约束的constant,如果有的话会覆盖你在display里的设置。 - 如果你用的是自动行高,确保所有元素的约束都是完整无歧义的,这样UITableView才能正确计算每一行的高度。
内容的提问来源于stack exchange,提问作者procra

