复用UICollectionViewCell导致drawRect内容叠加问题求助
解决UICollectionViewCell复用导致的图形叠加问题
看起来你遇到的问题确实是UICollectionView单元格复用带来的绘制残留问题。当cell被回收复用的时候,之前绘制的图形会留在上下文里,而你之前尝试的清除方式要么时机不对,要么没有彻底覆盖整个绘制区域。我来给你几个可行可行的解决方案:
方案一:修复Cell的绘制逻辑与复用重置
首先,我们需要在cell复用前重置状态,并在绘制的最开始彻底清除整个区域:
修改CardCell类
class CardCell: UICollectionViewCell { var card = Card() var cardLayout = CardLayout() // 关键:复用前重置数据,避免旧内容残留 override func prepareForReuse() { super.prepareForReuse() card = Card() // 重置为初始卡片状态 setNeedsDisplay() // 标记需要重新绘制 } override init(frame: CGRect) { super.init(frame: frame) layer.borderColor = UIColor.white.cgColor layer.borderWidth = 2 // 设置contentView背景为黑色,避免绘制前的默认背景干扰 contentView.backgroundColor = .black } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) contentView.backgroundColor = .black } func getCenter() -> CGPoint { return CGPoint(x: frame.size.width / 2, y: frame.size.height / 2) } override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // 第一步:彻底清除整个绘制区域,清空所有旧内容 context.clear(rect) // 第二步:绘制黑色背景(如果需要) context.setFillColor(UIColor.black.cgColor) context.fill(rect) // 第三步:绘制当前卡片的内容 cardLayout.render(c: context, card: card, frame: rect) } }
修改CardLayout的render方法
去掉循环里重复绘制黑色背景的代码(现在已经在draw开头统一处理了,避免重复操作):
func render(c: CGContext, card: Card, frame: CGRect) { let size = frame.size.height / 6 var num = quantity(i: card.quantity) var sy = size if num == 1 { sy = frame.size.height / 2 } else if num == 2 { sy = (frame.size.height / 2) - (size * 0.5) } else if num == 3 { sy = (frame.size.height / 2) - size } while num > 0 { c.beginPath() loadPath(c: c, card: card) if card.shape == 1 { render1(c: c, card: card, frame: frame, y: sy) } else if card.shape == 2 { render2(c: c, card: card, frame: frame, y: sy) } else if card.shape == 3 { // render3(c: c, card: card, frame: frame, y: sy) } drawPath(c: c, card: card) num = num - 1 sy = sy + size } }
方案二:分离绘制逻辑到独立View(更推荐)
把绘制逻辑放到一个单独的UIView子类里,这样可以把cell的复用逻辑和绘制逻辑彻底分开,减少出错概率,代码结构也更清晰:
创建CardDrawView
class CardDrawView: UIView { var card = Card() var cardLayout = CardLayout() override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // 清除旧内容 context.clear(rect) // 绘制背景 context.setFillColor(UIColor.black.cgColor) context.fill(rect) // 绘制卡片图形 cardLayout.render(c: context, card: card, frame: rect) } }
修改CardCell
class CardCell: UICollectionViewCell { // 用独立的绘制View代替直接在cell上绘制 private let drawView = CardDrawView() override init(frame: CGRect) { super.init(frame: frame) layer.borderColor = UIColor.white.cgColor layer.borderWidth = 2 // 添加绘制View到contentView,确保铺满整个cell contentView.addSubview(drawView) drawView.frame = contentView.bounds drawView.autoresizingMask = [.flexibleWidth, .flexibleHeight] } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) contentView.addSubview(drawView) drawView.frame = contentView.bounds drawView.autoresizingMask = [.flexibleWidth, .flexibleHeight] } override func prepareForReuse() { super.prepareForReuse() // 重置绘制View的卡片数据 drawView.card = Card() drawView.setNeedsDisplay() } // 提供外部设置卡片的便捷方法 func configure(with card: Card, layout: CardLayout) { drawView.card = card drawView.cardLayout = layout drawView.setNeedsDisplay() } }
修改cellForItemAt方法
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "card", for: indexPath) as! CardCell let targetCard = deck.active[indexPath.row] cell.configure(with: targetCard, layout: cardLayout) return cell }
为什么这样能解决问题?
- prepareForReuse的作用:这个方法是系统在cell即将被复用时调用的,在这里重置卡片数据,可以确保新的卡片内容不会和旧数据混合。
- context.clear(rect):这个方法会彻底清除当前绘制上下文的所有内容,比用黑色填充更彻底——填充只是覆盖,而clear是真正清空上下文缓冲区,从根源避免旧图形残留。
- 分离绘制逻辑:把绘制放到独立View里,每个cell的绘制区域都是独立的,复用的时候只需要更新这个View的状态,逻辑更清晰,也更容易排查后续问题。
内容的提问来源于stack exchange,提问作者quantumpotato
相关产品推荐
相关产品推荐

