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

Swift中如何反转Core Graphics Context绘图效果?

实现UIView绘图反转效果的解决方案

问题背景

现有基于UIView的绘图功能,通过lines数组存储用户绘制的路径信息,需实现点击按钮后反转绘图区域:原本绘制的区域隐藏,未绘制的区域显示。

最优解决方案:利用Core Graphics奇偶填充规则(Even-Odd)

这种方法无需修改现有lines数组结构,仅通过切换重绘逻辑实现反转,符合单次点击重绘的需求,效率更高。

步骤1:添加反转状态变量

在CanvasView类中新增布尔变量,控制当前是否处于反转模式:

class CanvasView: UIView {
    // ... 原有代码 ...
    var isInverted = false // 控制反转状态
    // ... 原有代码 ...
}

步骤2:修改inverse()方法

切换反转状态并触发重绘:

func inverse() {
    isInverted.toggle()
    setNeedsDisplay()
}

步骤3:重写draw(_ rect:)方法

根据反转状态切换渲染逻辑:

override func draw(_ rect: CGRect) {
    super.draw(rect)
    
    guard let context = UIGraphicsGetCurrentContext() else { return }
    
    if isInverted {
        // 反转模式:先填充整个视图为绘制色,再用奇偶规则挖掉已绘制路径
        context.setFillColor(strokeColor.cgColor)
        context.fill(rect)
        
        // 合并所有线条的描边路径(将线条的描边区域转为可填充的路径)
        let combinedPath = UIBezierPath()
        vm.lines.forEach { line in
            let path = UIBezierPath()
            guard let firstPoint = line.points.first else { return }
            path.move(to: firstPoint)
            line.points.dropFirst().forEach { path.addLine(to: $0) }
            
            // 配置线条样式(与正常绘制一致)
            path.lineWidth = line.strokeWidth
            path.lineCapStyle = .round
            path.lineJoinStyle = .round
            
            // 将描边路径转为填充路径,加入复合路径
            combinedPath.append(path.stroke())
        }
        
        // 使用奇偶裁剪规则,挖掉线条覆盖的区域
        context.saveGState()
        context.addPath(combinedPath.cgPath)
        context.clip(using: .evenOdd)
        
        // 填充背景色,实现“未绘制区域显示”的反转效果
        context.setFillColor(self.backgroundColor?.cgColor ?? UIColor.black.cgColor)
        context.fill(rect)
        
        context.restoreGState()
    } else {
        // 正常绘图模式:原有逻辑不变
        vm.lines.forEach { line in
            context.setStrokeColor(line.strokeColor.cgColor)
            context.setLineWidth(line.strokeWidth)
            context.setLineCap(.round)
            context.setLineJoin(.round)
            
            for (index, point) in line.points.enumerated() {
                if index == 0 {
                    context.move(to: point)
                } else {
                    context.addLine(to: point)
                }
            }
            context.strokePath()
        }
    }
}

原理说明

  • 反转模式下,先将整个视图填充为用户绘制时的颜色(比如白色)。
  • 把所有用户绘制的线条转换为描边区域的填充路径(因为线条是有宽度的,需要准确获取它覆盖的区域)。
  • 使用evenOdd裁剪规则:该规则会交替显示路径内外的区域,这里通过裁剪掉线条覆盖的区域,再填充背景色,最终实现“未绘制区域显示、已绘制区域隐藏”的反转效果。

不推荐的方案:基于Lines数组计算反转路径

直接计算未绘制区域(整个视图减去所有线条区域)的逻辑非常复杂,尤其是线条带有圆角描边时,需要处理大量路径运算,容易出现精度问题且性能较差,因此不建议采用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 17:10:19