如何访问在draw(_ rect: CGRect)中绘制的UIBezierPath?
解决draw(_ rect:)中绘制的UIBezierPath访问与移动问题
你在draw(_ rect:)中绘制的路径其实已经保存在自定义的bezierPaths数组里了——这就是你访问和修改路径的核心入口。要实现类似CAShapeLayer的触摸移动效果,只需要维护这个数组里的路径对象,在触摸操作时修改对应路径的位置,再触发视图重绘即可,具体步骤如下:
1. 给自定义路径添加触摸检测能力
UIBezierPath的默认contains(_:)方法仅检测填充区域,而你绘制的是线条,需要针对线宽扩大检测范围,方便触摸选中。给你的MyBezierPath添加扩展:
extension MyBezierPath { func isTouching(_ point: CGPoint) -> Bool { // 基于原路径创建加宽的检测路径,适配线条的触摸范围 let hitTestPath = UIBezierPath(cgPath: self.cgPath) hitTestPath.lineWidth = self.lineWidth + 10 // 额外增加10pt检测区域,提升触摸体验 return hitTestPath.contains(point) } }
2. 在视图中维护选中状态与触摸数据
在自定义视图里添加属性,用于跟踪选中的路径和触摸位置:
private var bezierPaths: [MyBezierPath] = [] private var selectedPath: MyBezierPath? // 当前选中的路径 private var lastTouchPosition: CGPoint? // 上一次触摸的坐标
3. 实现触摸事件处理逻辑
重写触摸方法,完成路径选中、移动更新的逻辑:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let touchPoint = touch.location(in: self) // 倒序遍历路径(后绘制的路径在视觉上层,优先检测) for path in bezierPaths.reversed() { if path.isTouching(touchPoint) { selectedPath = path lastTouchPosition = touchPoint break } } } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, let selectedPath = selectedPath, let lastPoint = lastTouchPosition else { return } let currentPoint = touch.location(in: self) // 计算触摸偏移量 let offsetX = currentPoint.x - lastPoint.x let offsetY = currentPoint.y - lastPoint.y // 平移选中的路径 selectedPath.apply(CGAffineTransform(translationX: offsetX, y: offsetY)) // 更新上一次触摸位置 lastTouchPosition = currentPoint // 触发视图重绘,更新界面 setNeedsDisplay() } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { // 触摸结束后清空选中状态 selectedPath = nil lastTouchPosition = nil }
4. 完善原有的draw(_ rect:)逻辑
你的draw(_ rect:)方法无需修改,只需在设置路径后触发重绘:
override func draw(_ rect: CGRect) { super.draw(rect) UIColor.orange.set() for path in bezierPaths { path.lineWidth = 4 path.lineCapStyle = .round path.lineJoinStyle = .round path.stroke() } } func drawingPath(_ path: [MyBezierPath]) { bezierPaths = path setNeedsDisplay() // 新增:设置路径后立即触发重绘 }
核心原理说明
CAShapeLayer能直接访问修改,是因为每个路径对应独立的图层;而draw(_ rect:)是把所有路径绘制到同一个视图的图形上下文里,没有单独的"图层载体"——所以必须通过你自己维护的bezierPaths数组管理所有路径,修改路径后通过setNeedsDisplay()触发重绘,让视图根据更新后的路径重新渲染。
内容的提问来源于stack exchange,提问作者Ramesh Sanghar
相关产品推荐
相关产品推荐

