使用子视图的笔势处理程序更新 UIView

Updating a UIView with a subview's gesture handler

提问人:vonbecker 提问时间:11/18/2023 最后编辑:vonbecker 更新时间:11/19/2023 访问量:31

问:

我有一个简单的应用程序,其中包含一个包含非常小的视图层次结构:(的子类)和三个(每个子类都是 )。ViewControllerDrawingViewUIViewImageViewsDrawingView

在里面,我已经覆盖了以生成依赖于每个图形中心的图形(具体来说,一个顶点是图像中心的多边形)。同时,位置由用户通过拖动手势控制。手势处理操作是 的方法。DrawingViewdraw(_:)ImageViewImageViewViewController

我想随着职位的变化实时更新。为了实现这一点,我在手势处理程序中调用了。但是,这种方法只会离散地更新,并且似乎直到下一个手势开始才更新(无论调用在语句中出现在哪里)。DrawingViewImageViewDrawingView.setNeedsDisplay()DrawingViewswitch gesture.state

simulator screengrab

我的问题是:我应该在哪里/如何打电话才能实现平滑(和实时)更新?或者有更好的方法吗?setNeedsDisplayDrawingView

以下是我的类定义:

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]!
    
    static var majorVertexXOffsets: [Double]?
    static var majorVertexYOffsets: [Double]?
        
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        majorVertices = [majorVertex1, majorVertex2, majorVertex3]
        majorVertexXConstraints = [majorVertex1XConstraint, majorVertex2XConstraint, majorVertex3XConstraint]
        majorVertexYConstraints = [majorVertex1YConstraint, majorVertex2YConstraint, majorVertex3YConstraint]
        ViewController.majorVertexXOffsets = majorVertexXConstraints.map {(constraint) -> Double in return constraint.constant}
        ViewController.majorVertexYOffsets = majorVertexYConstraints.map {(constraint) -> Double in return constraint.constant}
        
    }

    @IBAction func handlePan(_ gesture: UIPanGestureRecognizer) {
        
        guard let majorVertices = majorVertices,
              let gestureView = gesture.view
        else {return}
        
        guard let parentView = gestureView.superview,
              let gestureViewIndex = majorVertices.firstIndex(of: gestureView as! UIImageView)
        else {return}
        
        let translation = gesture.translation(in: parentView)
        
        switch gesture.state {
            
        case .began:
            ViewController.majorVertexXOffsets = majorVertexXConstraints.map {(constraint) -> Double in return constraint.constant}
            ViewController.majorVertexYOffsets = majorVertexYConstraints.map {(constraint) -> Double in return constraint.constant}
            break
            
        case .changed:
            majorVertexXConstraints[gestureViewIndex].constant = ViewController.majorVertexXOffsets![gestureViewIndex] + translation.x
            majorVertexYConstraints[gestureViewIndex].constant = ViewController.majorVertexYOffsets![gestureViewIndex] + translation.y
            drawingView.setNeedsDisplay()
            break
            
        case .ended, .cancelled:
            majorVertexXConstraints[gestureViewIndex].constant = gestureView.center.x - parentView.frame.size.width / 2.0
            majorVertexYConstraints[gestureViewIndex].constant = gestureView.center.y - parentView.frame.size.height / 2.0
            break

        default:
            break
            
        }
        
    }

}


class DrawingView: UIView {
    
    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(rect)
    }
    
    internal func drawTriangle(_ rect: CGRect) {
        
        guard let context = UIGraphicsGetCurrentContext(),
              let majorVertexXOffsets = ViewController.majorVertexXOffsets,
              let majorVertexYOffsets = ViewController.majorVertexYOffsets
        else {return}
        
        let majorVertexXCenters = majorVertexXOffsets.map {(x) -> Double in return x + rect.width / 2.0}
        let majorVertexYCenters = majorVertexYOffsets.map {(y) -> Double in return y + rect.height / 2.0}
                
        context.setStrokeColor(UIColor.lightGray.cgColor)
        context.setLineWidth(3)
        
        context.move(to: CGPoint(x: majorVertexXCenters[0], y: majorVertexYCenters[0]))
        context.addLine(to: CGPoint(x: majorVertexXCenters[1], y: majorVertexYCenters[1]))
        context.addLine(to: CGPoint(x: majorVertexXCenters[2], y: majorVertexYCenters[2]))
        context.addLine(to: CGPoint(x: majorVertexXCenters[0], y: majorVertexYCenters[0]))
        
        context.strokePath()
        
    }
    
}
iOS Swift UIGisgueRecognizer SetNeedsDisplay

评论


答:

2赞 DonMag 11/18/2023 #1

你正在用 vars 做一些相当时髦的事情,这需要直接引用特定的类......而且,目前还不完全清楚您是如何设置相对于视图的约束的,但是......static

您没有看到“实时”绘制更新的原因是,您在不更新 X 和 Y 偏移数组的情况下更改了约束常量

    case .changed:
        // modify constraint constants to "move" the view
        majorVertexXConstraints[gestureViewIndex].constant = ViewController.majorVertexXOffsets![gestureViewIndex] + translation.x
        majorVertexYConstraints[gestureViewIndex].constant = ViewController.majorVertexYOffsets![gestureViewIndex] + translation.y

        // update the arrays of X/Y offsets
        ViewController.majorVertexXOffsets = majorVertexXConstraints.map {(constraint) -> Double in return constraint.constant}
        ViewController.majorVertexYOffsets = majorVertexYConstraints.map {(constraint) -> Double in return constraint.constant}

        // NOW call setNeedsDisplay()
        drawingView.setNeedsDisplay()

        // because we're now updating the constraint constants on .changed
        // we need to reset the gesture translation
        gesture.setTranslation(.zero, in: parentView)
        
        break
        

编辑 - 回应评论...

你对 vars 所采用的方法导致了所谓的“紧密耦合”——两个或多个类严重依赖彼此,这可能使更改和/或重用类变得困难。static

这个问题太深入了,无法在这里完全讨论,但举个简单的例子:

在你的班级中,你有这两行:DrawingView

let majorVertexXOffsets = ViewController.majorVertexXOffsets
let majorVertexYOffsets = ViewController.majorVertexYOffsets

假设你想在 ?您必须编辑这两行:DrawingViewSomeOtherController

let majorVertexXOffsets = SomeOtherController.majorVertexXOffsets
let majorVertexYOffsets = SomeOtherController.majorVertexYOffsets

现在不再在原来的.DrawingViewViewController

相反,您要做的是添加 var 属性并根据需要设置/更新它们。DrawingView

因此,在删除关键字时:ViewControllerstatic

//static var majorVertexXOffsets: [Double]?
//static var majorVertexYOffsets: [Double]?
var majorVertexXOffsets: [Double]?
var majorVertexYOffsets: [Double]?

然后删除代码中出现的所有:ViewController.

//ViewController.majorVertexXOffsets
majorVertexXOffsets

接下来,我们将两个属性添加到:DrawingView

var majorVertexXOffsets: [Double]?
var majorVertexYOffsets: [Double]?

并且,更改以下部分的第一部分:drawTriangle()

//guard let context = UIGraphicsGetCurrentContext(),
//      let majorVertexXOffsets = ViewController.majorVertexXOffsets,
//      let majorVertexYOffsets = ViewController.majorVertexYOffsets
//else {return}
    
guard let context = UIGraphicsGetCurrentContext(),
      let majorVertexXOffsets = majorVertexXOffsets,
      let majorVertexYOffsets = majorVertexYOffsets
else {return}

最后一步是回到:ViewController

    case .changed:
        // modify constraint constants to "move" the view
        majorVertexXConstraints[gestureViewIndex].constant = majorVertexXOffsets![gestureViewIndex] + translation.x
        majorVertexYConstraints[gestureViewIndex].constant = majorVertexYOffsets![gestureViewIndex] + translation.y
        
        // update the arrays of X/Y offsets
        majorVertexXOffsets = majorVertexXConstraints.map {(constraint) -> Double in return constraint.constant}
        majorVertexYOffsets = majorVertexYConstraints.map {(constraint) -> Double in return constraint.constant}

        // NEW \/           
        // update the X/Y offset arrays in drawingView
        drawingView.majorVertexXOffsets = majorVertexXOffsets
        drawingView.majorVertexYOffsets = majorVertexYOffsets
        // NEW /\           
        
        // NOW call setNeedsDisplay()
        drawingView.setNeedsDisplay()
        
        // reset the gesture translation
        gesture.setTranslation(.zero, in: parentView)
        
        break
        

搜索以获得更深入的讨论。Tight Coupling Anti-Pattern

评论

0赞 vonbecker 11/19/2023
啊,谢谢你指出这一点!关于静态变量...我对这个东西真的是全新的,这是我弄清楚如何访问中定义的变量的唯一方法。建议?TIA系列ViewController
1赞 DonMag 11/19/2023
@vonbecker - 请参阅编辑我的答案。