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

iOS:基于CGPoint生成CGRect数组及实现擦除路径覆盖检测

iOS 擦除功能路径检测问题解决

问题背景

现有一组带红色标记的CGPoint数组,需用固定宽度的CGRect连接连续点(每个点为对应CGRect边的中点)生成CGRect数组。核心需求是实现擦除功能:已有存储起止点的绿色直线,用户绘制的紫色擦除路径需检测哪些绿色直线被完全覆盖并擦除。但直接调用CGPath.intersects()返回false,无法正确检测重叠。

核心问题

CGPath.intersects()检测的是矢量路径本身(线宽为0的几何线条),而非图层渲染出的带线宽的可视化区域。代码中绿色线宽10、紫色线宽15,但路径本身是细线条,视觉上重叠但几何检测不相交。

解决方案

一、生成连接连续点的CGRect数组

针对每对连续点p1、p2,计算对应固定宽度的CGRect(点为边中点):

  • 计算两点向量dx = p2.x - p1.x,dy = p2.y - p1.y
  • 计算线段长度length = sqrt(dx*dx + dy*dy),长度为0则跳过
  • 计算垂直于线段的单位向量,扩展得到线宽的半宽偏移
  • 取四个偏移点的边界生成CGRect

Swift实现代码:

extension CGPoint {
    func rectToPoint(_ other: CGPoint, lineWidth: CGFloat) -> CGRect? {
        let dx = other.x - x
        let dy = other.y - y
        let length = sqrt(dx*dx + dy*dy)
        guard length > .leastNonzeroMagnitude else { return nil }
        
        let halfWidth = lineWidth / 2
        let perpX = -dy / length * halfWidth
        let perpY = dx / length * halfWidth
        
        let minX = min(x + perpX, x - perpX, other.x + perpX, other.x - perpX)
        let maxX = max(x + perpX, x - perpX, other.x + perpX, other.x - perpX)
        let minY = min(y + perpY, y - perpY, other.y + perpY, other.y - perpY)
        let maxY = max(y + perpY, y - perpY, other.y + perpY, other.y - perpY)
        
        return CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
    }
}

// 使用示例
let points: [CGPoint] = [.init(x:50,y:50), .init(x:50,y:400), .init(x:300,y:400), .init(x:300,y:50)]
let lineWidth: CGFloat = 10
var rects: [CGRect] = []
for i in 0..<points.count-1 {
    if let rect = points[i].rectToPoint(points[i+1], lineWidth: lineWidth) {
        rects.append(rect)
    }
}

二、正确的覆盖检测逻辑

将可视化的线条区域转换成填充路径,再做包含/相交检测:

  1. 把绿色每条线段的可视化区域转换成填充路径
  2. 把紫色擦除路径的可视化区域转换成填充路径
  3. 检测绿色线段的填充路径是否完全包含在紫色填充路径内

修改后的完整代码:

public override func draw(_ rect: CGRect) {
    // 绿色墙壁配置
    let wallPoints: [CGPoint] = [.init(x:50,y:50), .init(x:50,y:400), .init(x:300,y:400), .init(x:300,y:50)]
    let wallLineWidth: CGFloat = 10
    
    // 绘制绿色墙壁
    let wallLayer = CAShapeLayer()
    let wallPath = UIBezierPath()
    for i in 0..<wallPoints.count-1 {
        wallPath.move(to: wallPoints[i])
        wallPath.addLine(to: wallPoints[i+1])
    }
    wallLayer.path = wallPath.cgPath
    wallLayer.lineWidth = wallLineWidth
    wallLayer.strokeColor = UIColor.green.cgColor
    wallLayer.fillColor = nil
    layer.addSublayer(wallLayer)
    
    // 紫色擦除路径配置
    let eraserPoints: [CGPoint] = [.init(x:40,y:75), .init(x:120,y:75)]
    let eraserLineWidth: CGFloat = 15
    
    // 绘制紫色擦除线
    let eraserLayer = CAShapeLayer()
    let eraserPath = UIBezierPath()
    eraserPath.move(to: eraserPoints[0])
    eraserPath.addLine(to: eraserPoints[1])
    eraserLayer.path = eraserPath.cgPath
    eraserLayer.lineWidth = eraserLineWidth
    eraserLayer.strokeColor = UIColor.purple.cgColor
    layer.addSublayer(eraserLayer)
    
    // 检测每条绿色线段的覆盖情况
    for i in 0..<wallPoints.count-1 {
        let p1 = wallPoints[i]
        let p2 = wallPoints[i+1]
        
        // 生成绿色线段的填充区域路径
        guard let wallRect = p1.rectToPoint(p2, lineWidth: wallLineWidth) else { continue }
        let wallFillPath = UIBezierPath(rect: wallRect)
        
        // 生成紫色擦除路径的填充区域路径
        guard let eraserRect = eraserPoints[0].rectToPoint(eraserPoints[1], lineWidth: eraserLineWidth) else { continue }
        let eraserFillPath = UIBezierPath(rect: eraserRect)
        
        // 检测完全覆盖:绿色区域边界在紫色区域内,且路径完全包含
        let isFullyCovered = eraserFillPath.contains(wallFillPath.bounds) &&
                             eraserFillPath.cgPath.contains(wallFillPath.cgPath)
        
        if isFullyCovered {
            print("第\(i+1)条绿色线段被完全覆盖,可擦除")
        } else {
            // 可选:检测部分覆盖
            if wallFillPath.cgPath.intersects(eraserFillPath.cgPath) {
                print("第\(i+1)条绿色线段部分覆盖")
            } else {
                print("第\(i+1)条绿色线段未覆盖")
            }
        }
    }
}

曲线场景补充

如果紫色路径是曲线或多段线,用CGPath.copy(strokingWithWidth:)生成准确的描边填充路径:

func strokedPath(from path: UIBezierPath, lineWidth: CGFloat) -> CGPath {
    return path.cgPath.copy(strokingWithWidth: lineWidth,
                            lineCap: .round,
                            lineJoin: .round,
                            miterLimit: 0)
}

用该方法生成的路径做检测,能适配曲线、圆角线帽等复杂场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:54:54