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
相关产品推荐
相关产品推荐

