当我结合 UIPanGestureRecognizer 和自动布局时,我的 UIViews 搞砸了
my UIViews muck-up when I combine UIPanGestureRecognizer and autolayout
我想要一个球来跟踪我的手指,因为我在 iPhone 或 iPad 上为每个允许的设备方向沿着圆形轨迹拖动它。当设备旋转时,视图似乎正确居中,但球不会停留在圆周上,当我拖动它时似乎会去任何地方。
编辑
现在根据需要显示。我唯一的额外代码更改是删除不必要的声明 var shapeLayer = CAShapeLayer()
made perfect sense until I tried constraining both ball and trajectory to the view's centre and adding the ball’s centre coordinates as offsets at run time. I followed 中的数学。
有三件事我不明白。
首先,根据两个变量trackRadius
和角度theta
计算圆的周长,并使用theta
的sin
和cos
求出x
和 y
坐标不会将球放在正确的位置。
其次,使用atan
求视图中心与触摸点之间的角度theta
,使用trackRadius
和theta
求x
和 y
坐标不会将球放置或移动到圆周上的新位置。
第三,每当我拖动球时,调试区域中都会显示一条消息 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
没有考虑 center 的视图(或轨迹),并且 atan2()
的参数是错误的(y 排在第一位)。应该是:
theta = atan2(finger.y - track.center.y, finger.x - track.center.x)
另一个问题是你添加的约束越来越多
在 func constrainBall()
中,而不删除以前的。
您应该保留对约束的引用并修改它们。
最后注意球的width/height约束应该是2*ballRadius
,而不是trackRadius
。
将它们放在一起(并删除一些不必要的类型
转换),它看起来像这样:
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)
}
我想要一个球来跟踪我的手指,因为我在 iPhone 或 iPad 上为每个允许的设备方向沿着圆形轨迹拖动它。当设备旋转时,视图似乎正确居中,但球不会停留在圆周上,当我拖动它时似乎会去任何地方。
编辑
var shapeLayer = CAShapeLayer()
有三件事我不明白。
首先,根据两个变量trackRadius
和角度theta
计算圆的周长,并使用theta
的sin
和cos
求出x
和 y
坐标不会将球放在正确的位置。
其次,使用atan
求视图中心与触摸点之间的角度theta
,使用trackRadius
和theta
求x
和 y
坐标不会将球放置或移动到圆周上的新位置。
第三,每当我拖动球时,调试区域中都会显示一条消息 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
没有考虑 center 的视图(或轨迹),并且 atan2()
的参数是错误的(y 排在第一位)。应该是:
theta = atan2(finger.y - track.center.y, finger.x - track.center.x)
另一个问题是你添加的约束越来越多
在 func constrainBall()
中,而不删除以前的。
您应该保留对约束的引用并修改它们。
最后注意球的width/height约束应该是2*ballRadius
,而不是trackRadius
。
将它们放在一起(并删除一些不必要的类型 转换),它看起来像这样:
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)
}