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

Swift中使用CALayer绘图时连续触摸点异常问题

解决CALayer实现纹理绘图时快速滑动线条断开的问题

在Swift中使用CALayer替代UIBezierPath实现基于图片纹理的手指滑动绘图时,出现了滑动速度相关的异常:慢速滑动时纹理点能连续衔接,绘图正常;但快速滑动时触摸点无法连续,线条出现多处空白断开。当前实现代码如下:

class CustomStrokeDrawingView: UIView {
    var path = UIBezierPath()
    var startPoint = CGPoint()
    var touchPoint = CGPoint()
    var shape =  UIImage(named:"square-outline")

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if let point = touch?.location(in: self) {
            startPoint = point
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        if let point = touch?.location(in: self) {
            touchPoint = point
        }

        let imageLayer = CALayer()
        imageLayer.backgroundColor = UIColor.clear.cgColor
        imageLayer.bounds = CGRect(x: startPoint.x, y: startPoint.y , width: 20, height: 20)
        imageLayer.position = CGPoint(x:touchPoint.x ,y:touchPoint.y)
        imageLayer.contents = shape?.cgImage
        self.layer.addSublayer(imageLayer)
        
        
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
    }
}

问题原因

  • iOS系统会根据触摸滑动速度调整touchesMoved的触发频率:快速滑动时,两个连续触摸点的间距会显著变大。原代码仅在每个触发的触摸点位置添加单个CALayer,没有填充点与点之间的空隙,导致线条断开。
  • UIBezierPath天然支持连续路径绘制,会自动处理点间距问题,而CALayer是离散的独立图层,必须手动处理点间衔接逻辑。

解决方案:填充触摸点间的空隙

通过计算连续触摸点的距离,按固定步长在两点之间均匀添加纹理图层,确保无论滑动速度快慢,纹理都能连续衔接。

优化后的代码

class CustomStrokeDrawingView: UIView {
    var lastTouchPoint: CGPoint?
    let shape = UIImage(named: "square-outline")
    let textureSize: CGFloat = 20 // 纹理的固定尺寸

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        lastTouchPoint = touch.location(in: self)
        // 在起始点添加第一个纹理
        addTexture(at: lastTouchPoint!)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first, let startPoint = lastTouchPoint else { return }
        let endPoint = touch.location(in: self)
        
        // 计算两点间距离,按纹理重叠比例计算需要填充的纹理数量
        let distance = hypot(endPoint.x - startPoint.x, endPoint.y - startPoint.y)
        guard distance > 0 else { return }
        
        let overlapRatio: CGFloat = 0.8 // 控制纹理重叠程度,避免缝隙
        let stepCount = Int(distance / (textureSize * overlapRatio))
        let stepX = (endPoint.x - startPoint.x) / CGFloat(stepCount)
        let stepY = (endPoint.y - startPoint.y) / CGFloat(stepCount)
        
        // 逐点添加纹理
        for i in 0...stepCount {
            let currentPoint = CGPoint(
                x: startPoint.x + stepX * CGFloat(i),
                y: startPoint.y + stepY * CGFloat(i)
            )
            addTexture(at: currentPoint)
        }
        
        lastTouchPoint = endPoint
    }
    
    /// 统一创建并添加纹理图层
    private func addTexture(at point: CGPoint) {
        let imageLayer = CALayer()
        imageLayer.backgroundColor = UIColor.clear.cgColor
        imageLayer.bounds = CGRect(x: 0, y: 0, width: textureSize, height: textureSize)
        imageLayer.position = point
        imageLayer.contents = shape?.cgImage
        self.layer.addSublayer(imageLayer)
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
    }
}

关键优化点

  • 记录上一个触摸点lastTouchPoint,用于计算当前点与历史点的间距
  • 根据纹理尺寸和重叠比例,动态计算需要填充的纹理数量,确保点间无空隙
  • 修正原代码中bounds的错误设置:将bounds原点设为(0,0),通过position控制图层中心位置,避免偏移
  • 封装addTexture方法,统一图层创建逻辑,提升代码可维护性

额外性能优化建议

如果需要绘制大量纹理,避免因图层过多导致性能下降,可考虑:

  • 使用CAShapeLayer结合UIBezierPath绘制路径,通过strokePattern属性设置纹理图案,性能优于添加大量独立CALayer
  • 将绘制内容缓存到UIImage中,定期合并图层,减少图层数量

内容的提问来源于stack exchange,提问作者Rezwan Khan chowdhury

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 20:35:26