如何在Swift中创建支持Force Touch压力感应的画笔?
实现Force Touch控制的平滑画笔(Swift)
核心改进思路
针对现有代码的性能问题和平滑过渡需求,采用以下方案:
- 用归一化压力值统一不同设备的Force Touch输入范围
- 用缓存画布替代大量CALayer创建,提升绘制性能
- 通过插值点渐变实现触摸点间的粗细/透明度平滑过渡
- 添加收尾渐变点实现笔触结束时的自然淡出
完整实现代码
import UIKit struct TouchPoint { let location: CGPoint let force: CGFloat // 归一化后的压力值(0-1) } class DrawingView: UIView { // 画笔配置 private let brushImage: UIImage? = UIImage(named: "square-outline") private let minBrushWidth: CGFloat = 5 private let maxBrushWidth: CGFloat = 30 private let minOpacity: CGFloat = 0.2 private let maxOpacity: CGFloat = 1.0 // 绘制数据 private var touchPoints: [TouchPoint] = [] private var canvasImage: UIImage? // 缓存已绘制内容 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let normalizedForce = min(touch.force / touch.maximumPossibleForce, 1.0) touchPoints.append(TouchPoint( location: touch.location(in: self), force: normalizedForce )) setNeedsDisplay() } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let normalizedForce = min(touch.force / touch.maximumPossibleForce, 1.0) touchPoints.append(TouchPoint( location: touch.location(in: self), force: normalizedForce )) setNeedsDisplay() } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard let lastPoint = touchPoints.last else { return } // 添加5个渐变收尾点,实现笔触淡出 let fadeSteps = 5 for step in 1...fadeSteps { let fadeForce = lastPoint.force * CGFloat(fadeSteps - step) / CGFloat(fadeSteps) touchPoints.append(TouchPoint( location: lastPoint.location, force: fadeForce )) } setNeedsDisplay() // 合并当前绘制内容到缓存画布 mergeToCanvas() touchPoints.removeAll() } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { touchesEnded(touches, with: event) } override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext(), let brushImage = brushImage else { return } // 先绘制缓存的已完成内容 canvasImage?.draw(in: bounds) // 绘制当前触摸路径的平滑过渡 guard touchPoints.count > 1 else { return } for i in 1..<touchPoints.count { let prev = touchPoints[i-1] let current = touchPoints[i] // 计算当前点的画笔参数 let currentWidth = minBrushWidth + (maxBrushWidth - minBrushWidth) * current.force let currentOpacity = minOpacity + (maxOpacity - minOpacity) * current.force // 绘制当前点的画笔 let brushRect = CGRect( x: current.location.x - currentWidth/2, y: current.location.y - currentWidth/2, width: currentWidth, height: currentWidth ) brushImage.draw(in: brushRect, blendMode: .normal, alpha: currentOpacity) // 插值绘制中间点,实现平滑过渡 let distance = hypot(current.location.x - prev.location.x, current.location.y - prev.location.y) let interpolateSteps = Int(distance / 2) // 每2像素插入一个过渡点 guard interpolateSteps > 0 else { continue } for step in 1..<interpolateSteps { let t = CGFloat(step) / CGFloat(interpolateSteps) let interpolatedX = prev.location.x + t * (current.location.x - prev.location.x) let interpolatedY = prev.location.y + t * (current.location.y - prev.location.y) let interpolatedForce = prev.force + t * (current.force - prev.force) let interpolatedWidth = minBrushWidth + (maxBrushWidth - minBrushWidth) * interpolatedForce let interpolatedOpacity = minOpacity + (maxOpacity - minOpacity) * interpolatedForce let interpolatedRect = CGRect( x: interpolatedX - interpolatedWidth/2, y: interpolatedY - interpolatedWidth/2, width: interpolatedWidth, height: interpolatedWidth ) brushImage.draw(in: interpolatedRect, blendMode: .normal, alpha: interpolatedOpacity) } } } // 将当前临时绘制内容合并到缓存画布 private func mergeToCanvas() { UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale) canvasImage?.draw(in: bounds) drawHierarchy(in: bounds, afterScreenUpdates: true) canvasImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } }
关键实现细节
压力值归一化
- 使用
touch.force / touch.maximumPossibleForce将压力值转换为0-1的统一范围,避免不同设备的压力阈值差异 - 用
min(_:_:)确保值不会超过1.0
- 使用
平滑过渡处理
- 在
touchesEnded中添加渐变收尾点,让笔触从当前压力平滑降到0,避免突兀结束 - 在绘制时对相邻触摸点进行插值,生成过渡点,确保粗细和透明度的变化连续自然
- 在
性能优化
- 用
canvasImage缓存已完成的绘制内容,每次只绘制最新的触摸路径,避免重复绘制 - 替代原方案中大量创建CALayer的方式,减少内存占用和图层渲染开销
- 用
可配置参数
minBrushWidth/maxBrushWidth:控制画笔粗细的范围minOpacity/maxOpacity:控制画笔透明度的范围- 可根据需求调整这些参数,适配不同的画笔风格
内容的提问来源于stack exchange,提问作者Rezwan Khan chowdhury
相关产品推荐
相关产品推荐

