You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

复用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 
}

为什么这样能解决问题?

  1. prepareForReuse的作用:这个方法是系统在cell即将被复用时调用的,在这里重置卡片数据,可以确保新的卡片内容不会和旧数据混合。
  2. context.clear(rect):这个方法会彻底清除当前绘制上下文的所有内容,比用黑色填充更彻底——填充只是覆盖,而clear是真正清空上下文缓冲区,从根源避免旧图形残留。
  3. 分离绘制逻辑:把绘制放到独立View里,每个cell的绘制区域都是独立的,复用的时候只需要更新这个View的状态,逻辑更清晰,也更容易排查后续问题。

内容的提问来源于stack exchange,提问作者quantumpotato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:57:51