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) } }
二、正确的覆盖检测逻辑
将可视化的线条区域转换成填充路径,再做包含/相交检测:
- 把绿色每条线段的可视化区域转换成填充路径
- 把紫色擦除路径的可视化区域转换成填充路径
- 检测绿色线段的填充路径是否完全包含在紫色填充路径内
修改后的完整代码:
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
相关产品推荐
相关产品推荐

