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

如何访问在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 15:03:34