当我将UIPanGestureRecognizer和自动布局结合在一起时,我的UIViews变得糟糕透顶

我想要一个球跟踪我的手指,当我沿着一个圆形轨迹拖动iPhone或iPad上的每个允许的设备方向时。 当一个设备被旋转时,视图看起来是正确的居中,但是球不会停留在圆周上,当我拖动它的时候它似乎会去任何地方。


编辑

马丁R的答案现在显示这个要求。 我唯一的附加代码更改是删除不必要的声明var shapeLayer = CAShapeLayer()

在这里输入图像说明


这个例子中的math是非常有意义的,直到我试图将球和轨迹同时约束到视图的中心,并在运行时将球的中心坐标加上偏移量。 我遵循这些关于如何约束视图的build议 。

有三件事我不明白。

首先,根据两个variablestrackRadius和angular度theta计算圆的圆周,并使用theta sincos来找出xy坐标,这样就不会使球处于正确的位置。

其次,使用atan来查看视图中心与触摸点之间的angular度theta ,使用trackRadiustheta来查找xy坐标不会将球放置或移动到沿着圆周的新位置。

第三,每当我拖动球,在debugging区域的消息说, Xcode is Unable to simultaneously satisfy constraints ,虽然没有约束问题报告拖动之前。

这里可能有一个以上的问题。 我的大脑开始受到伤害,如果有人能指出我做错了什么,我会很感激。

这是我的代码。

 import UIKit class ViewController: UIViewController { override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .all } var shapeLayer = CAShapeLayer() let track = ShapeView() var ball = ShapeView() var theta = CGFloat() private let trackRadius: CGFloat = 125 private let ballRadius: CGFloat = 10 override func viewDidLoad() { super.viewDidLoad() createTrack() createBall() } private func createTrack() { track.translatesAutoresizingMaskIntoConstraints = false track.shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: -trackRadius, y: -trackRadius, width: 2 * trackRadius, height: 2 * trackRadius)).cgPath track.shapeLayer.fillColor = UIColor.clear.cgColor track.shapeLayer.strokeColor = UIColor.red.cgColor view.addSubview(track) track.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true track.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true } private func createBall() { let offset = placeBallOnCircumference() drawBall() constrainBall(offset: offset) let touch = UIPanGestureRecognizer(target: self, action:#selector(dragBall(recognizer:))) view.addGestureRecognizer(touch) } private func placeBallOnCircumference() -> CGPoint { let theta: Double = 0 // at 0 radians let x = CGFloat(cos(theta)) * trackRadius // find x and y coords on let y = CGFloat(sin(theta)) * trackRadius // circle circumference return CGPoint(x: x, y: y) } func dragBall(recognizer: UIPanGestureRecognizer) { var offset = CGPoint() let finger : CGPoint = recognizer.location(in: self.view) theta = CGFloat(atan2(Double(finger.x), Double(finger.y))) // get angle from finger tip to centre offset.x = CGFloat(cos(theta)) * trackRadius // use angle and radius to get x and offset.y = CGFloat(sin(theta)) * trackRadius // y coords on circle circumference drawBall() constrainBall(offset: offset) } private func drawBall() { ball.shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 2 * ballRadius, height: 2 * ballRadius)).cgPath ball.shapeLayer.fillColor = UIColor.cyan.cgColor ball.shapeLayer.strokeColor = UIColor.black.cgColor view.addSubview(ball) } private func constrainBall(offset: CGPoint) { ball.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ ball.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: offset.x), ball.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: offset.y), ball.widthAnchor.constraint(equalToConstant: trackRadius), ball.heightAnchor.constraint(equalToConstant: trackRadius) ]) } } 

主要的错误是

 theta = CGFloat(atan2(Double(finger.x), Double(finger.y))) // get angle from finger tip to centre 

不考虑视图(或跟踪) 中心 ,并且atan2()的参数是错误的(y先出现)。 它应该是:

 theta = atan2(finger.y - track.center.y, finger.x - track.center.x) 

另一个问题是你在func constrainBall()添加了越来越多的func constrainBall() ,而没有删除以前的那些。 你应该保持对约束的引用,并修改它们。

最后请注意,球的宽度/高度约束应该是2*ballRadius ,而不是trackRadius

把它放在一起(并删除一些不必要的types转换),它会看起来像这样:

 var ballXconstraint: NSLayoutConstraint! var ballYconstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() createTrack() createBall() let touch = UIPanGestureRecognizer(target: self, action:#selector(dragBall(recognizer:))) view.addGestureRecognizer(touch) } private func createTrack() { track.translatesAutoresizingMaskIntoConstraints = false track.shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 2 * trackRadius, height: 2 * trackRadius)).cgPath track.shapeLayer.fillColor = UIColor.clear.cgColor track.shapeLayer.strokeColor = UIColor.red.cgColor view.addSubview(track) track.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true track.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true track.widthAnchor.constraint(equalToConstant: 2 * trackRadius).isActive = true track.heightAnchor.constraint(equalToConstant: 2 * trackRadius).isActive = true } private func createBall() { // Create ball: ball.translatesAutoresizingMaskIntoConstraints = false ball.shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 2 * ballRadius, height: 2 * ballRadius)).cgPath ball.shapeLayer.fillColor = UIColor.cyan.cgColor ball.shapeLayer.strokeColor = UIColor.black.cgColor view.addSubview(ball) // Width/Height contraints: ball.widthAnchor.constraint(equalToConstant: 2 * ballRadius).isActive = true ball.heightAnchor.constraint(equalToConstant: 2 * ballRadius).isActive = true // X/Y constraints: let offset = pointOnCircumference(0.0) ballXconstraint = ball.centerXAnchor.constraint(equalTo: track.centerXAnchor, constant: offset.x) ballYconstraint = ball.centerYAnchor.constraint(equalTo: track.centerYAnchor, constant: offset.y) ballXconstraint.isActive = true ballYconstraint.isActive = true } func dragBall(recognizer: UIPanGestureRecognizer) { let finger = recognizer.location(in: self.view) // Angle from track center to touch location: theta = atan2(finger.y - track.center.y, finger.x - track.center.x) // Update X/Y contraints of the ball: let offset = pointOnCircumference(theta) ballXconstraint.constant = offset.x ballYconstraint.constant = offset.y } private func pointOnCircumference(_ theta: CGFloat) -> CGPoint { let x = cos(theta) * trackRadius let y = sin(theta) * trackRadius return CGPoint(x: x, y: y) }