如何通过子视图手势处理器实时平滑更新UIView?
解决DrawingView实时平滑更新的问题
问题根源
你当前的实现有两个核心问题导致更新不流畅:
- 约束修改后,系统不会立即更新视图布局,调用
setNeedsDisplay时,ImageView的实际位置还没同步,导致draw(_:)里用的还是旧坐标。 DrawingView依赖ViewController的静态变量获取顶点位置,这种耦合方式不仅不安全,还无法实时获取视图的最新位置。
修复方案(两种可选)
方案一:优化现有draw(_:)实现,实时获取顶点位置
直接让DrawingView持有三个顶点ImageView的引用,在绘制时实时读取它们的中心坐标,同时在手势回调里确保布局更新后再触发重绘。
修改后的代码:
class ViewController: UIViewController { @IBOutlet var drawingView: DrawingView! @IBOutlet var majorVertex1: UIImageView! @IBOutlet var majorVertex2: UIImageView! @IBOutlet var majorVertex3: UIImageView! var majorVertices: [UIImageView]! @IBOutlet var majorVertex1XConstraint: NSLayoutConstraint! @IBOutlet var majorVertex1YConstraint: NSLayoutConstraint! @IBOutlet var majorVertex2XConstraint: NSLayoutConstraint! @IBOutlet var majorVertex2YConstraint: NSLayoutConstraint! @IBOutlet var majorVertex3XConstraint: NSLayoutConstraint! @IBOutlet var majorVertex3YConstraint: NSLayoutConstraint! var majorVertexXConstraints: [NSLayoutConstraint]! var majorVertexYConstraints: [NSLayoutConstraint]! // 移除静态变量 var majorVertexXOffsets: [Double]? var majorVertexYOffsets: [Double]? override func viewDidLoad() { super.viewDidLoad() majorVertices = [majorVertex1, majorVertex2, majorVertex3] majorVertexXConstraints = [majorVertex1XConstraint, majorVertex2XConstraint, majorVertex3XConstraint] majorVertexYConstraints = [majorVertex1YConstraint, majorVertex2YConstraint, majorVertex3YConstraint] // 初始化偏移量 majorVertexXOffsets = majorVertexXConstraints.map { $0.constant } majorVertexYOffsets = majorVertexYConstraints.map { $0.constant } // 给DrawingView传递顶点视图引用 drawingView.vertexViews = majorVertices } @IBAction func handlePan(_ gesture: UIPanGestureRecognizer) { guard let gestureView = gesture.view as? UIImageView, let parentView = gestureView.superview, let index = majorVertices.firstIndex(of: gestureView) else { return } let translation = gesture.translation(in: parentView) switch gesture.state { case .began: // 记录初始偏移 majorVertexXOffsets = majorVertexXConstraints.map { $0.constant } majorVertexYOffsets = majorVertexYConstraints.map { $0.constant } case .changed: // 更新约束 majorVertexXConstraints[index].constant = majorVertexXOffsets![index] + translation.x majorVertexYConstraints[index].constant = majorVertexYOffsets![index] + translation.y // 强制更新布局,确保ImageView位置生效 parentView.layoutIfNeeded() // 触发DrawingView重绘 drawingView.setNeedsDisplay() case .ended, .cancelled: // 修正约束值,确保后续布局正确 majorVertexXConstraints[index].constant = gestureView.center.x - parentView.frame.width / 2.0 majorVertexYConstraints[index].constant = gestureView.center.y - parentView.frame.height / 2.0 default: break } } } class DrawingView: UIView { // 添加顶点视图引用 var vertexViews: [UIImageView]? override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() } func setupView() { backgroundColor = .clear } override func draw(_ rect: CGRect) { super.draw(rect) drawTriangle() } private func drawTriangle() { guard let context = UIGraphicsGetCurrentContext(), let vertices = vertexViews, vertices.count == 3 else { return } // 实时获取每个ImageView的中心坐标 let points = vertices.map { $0.center } context.setStrokeColor(UIColor.lightGray.cgColor) context.setLineWidth(3) context.move(to: points[0]) context.addLine(to: points[1]) context.addLine(to: points[2]) context.closePath() // 替代手动回到起点,更简洁 context.strokePath() } }
方案二:用CAShapeLayer替代draw(_:)(更优,性能更好)
直接在DrawingView里创建CAShapeLayer,通过更新图层路径实现平滑动画,无需每次调用setNeedsDisplay。
修改后的代码:
class ViewController: UIViewController { // 代码同方案一的ViewController部分,无需改动 } class DrawingView: UIView { var vertexViews: [UIImageView]? { didSet { // 顶点视图变化时更新路径 updateTrianglePath() } } private let shapeLayer = CAShapeLayer() override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() } func setupView() { backgroundColor = .clear shapeLayer.strokeColor = UIColor.lightGray.cgColor shapeLayer.lineWidth = 3 shapeLayer.fillColor = UIColor.clear.cgColor layer.addSublayer(shapeLayer) } // 提供外部调用的更新方法 func updateTrianglePath() { guard let vertices = vertexViews, vertices.count == 3 else { shapeLayer.path = nil return } let path = UIBezierPath() let points = vertices.map { $0.center } path.move(to: points[0]) path.addLine(to: points[1]) path.addLine(to: points[2]) path.close() shapeLayer.path = path.cgPath } }
然后在ViewController的手势.changed分支里,替换drawingView.setNeedsDisplay()为:
parentView.layoutIfNeeded() drawingView.updateTrianglePath()
关键说明
layoutIfNeeded():强制立即更新视图布局,确保ImageView的位置在重绘/更新路径前已经生效。- 移除静态变量:通过直接持有视图引用或属性传递,避免耦合,保证数据实时性。
- CAShapeLayer方案:相比
draw(_:),图层更新更高效,系统会自动处理动画过渡,平滑度更好。
内容的提问来源于stack exchange,提问作者vonbecker
相关产品推荐
相关产品推荐

