当我结合使用UIPanGestureRecognizer和自动布局时,我的UIViews混乱了 [英] my UIViews muck-up when I combine UIPanGestureRecognizer and autolayout
问题描述
当我在iPhone或iPad上为每个允许的设备方向拖动它时,我想要一个球来跟踪我的手指。当设备旋转时,视图似乎正确居中,但是当我拖动它时,球不会停留在圆周上并且似乎在任何地方。
编辑
现在
这个例子非常有意义,直到我尝试将球和轨迹约束到视图的中心并在运行时将球的中心坐标添加为偏移。我按照这些关于如何约束视图的建议。
有三件事我不明白。
首先,从两个变量 trackRadius
和角度 theta $计算圆周长c $ c>并使用
sin
和 cos
theta
找到 x
和 y
坐标不会将球放在正确的位置。
其次,使用 atan
查找角度 theta
在视图中心和触摸点之间,并使用 trackRadius
与 theta
查找 x
和 y
坐标不会放置或将球移动到圆周的新位置。
第三,每当我拖动球时,调试区域中的一条消息说 Xcode无法同时满足约束
,虽然在拖动之前没有报告任何约束问题。
这里可能存在多个问题。我的大脑开始受到伤害,如果有人能指出我做错了什么,我将不胜感激。
这是我的代码。
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 {
让theta:Double = 0 // 0弧度
让x = CGFloat(cos(theta))* trackRadius //找到
上的x和y坐标让y = CGFloat( sin(theta))* trackRadius //圆周
返回CGPoint(x:x,y:y)
}
func dragBall(识别器:UIPanGestureRecognizer){
var offset = CGPoint()
let finger:CGPoint = recognizer.location(in:self.view)
theta = CGFloat(atan2(Double(finger.x) ,Double(finger.y)))//从指尖到中心获得角度
offset.x = CGFloat(cos(theta))* trackRadius //使用角度和半径来获得x和
偏移.y = CGFloat(sin(theta))* trackRadius // y圆圈上的坐标
drawBall()
constrainBall(offset:offset)
}
private func drawBall(){
ball.shapeLayer.path = UIBezierPath(ovalIn:CGRect) (x:0,y:0,宽度:2 * ballRadius,高度: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)))//从指尖到中心获得角度
不考虑(或跟踪) center ,并且 atan2()
的参数是错误的方法(y先来)。它应该是:
theta = atan2(finger.y - track.center.y,finger.x - track.center。 x)
另一个问题是你在<$ c $中添加越来越多的约束
c> func constrainBall(),不删除以前的。
你应该保留对约束的引用并改为修改它们。
最后请注意,球的宽度/高度约束应该是 2 * ballRadius
,而非 trackRadius
。
将所有内容放在一起(并删除一些不必要的类型
转换),它看起来像这样:
var ballXconstraint:NSLayoutConstraint!
var ballYconstraint:NSLayoutConstraint!
覆盖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(){
//创建球:
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)
//宽度/高度限制:
ball.widthAnchor.constraint(equalToConstant :2 * ballRadius).isActive = true
ball.heightAnchor.constraint(equalToConstant:2 * ballRadius).isActive = true
// X / Y约束:
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(识别器:UIPanGestureRecognizer){
let finger = recognizer.location(in:self.view)
//从轨道中心到触摸位置的角度:
theta = atan2(finger.y - track.center.y,finger.x - track.center.x)
//更新球的X / Y限制:
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
返回CGPoint(x:x,y:y)
}
I’d like a ball to track my finger as I drag it along a circular trajectory for every allowable device orientation on iPhone or iPad. Views appear to be correctly centred when a device is rotated but the ball will not stay on the circumference and seems to go anywhere when I drag it.
EDIT
Martin R's answer now displays this as required. My only additional code change was to remove an unnecessary declaration var shapeLayer = CAShapeLayer()
The maths in this example 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 these recommendations on how to constrain a view.
There are three things I don’t understand.
First, calculating the circle’s circumference from two variables trackRadius
and angle theta
and using sin
and cos
of theta
to find x
and y
coordinates will not place the ball in the right position.
Second, using atan
to find the angle theta
between the view centre and the point touched, and using trackRadius
with theta
to find x
and y
coordinates will not place or move the ball to a new place along the circumference.
And third, whenever I drag the ball, a message in the debug area says that Xcode is Unable to simultaneously satisfy constraints
, although no constraints problems are reported prior to dragging it.
There may be more than one problem here. My brain is starting to hurt and I’d be grateful if someone could point out what I have done wrong.
Here is my code.
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)
])
}
}
The main error is that
theta = CGFloat(atan2(Double(finger.x), Double(finger.y))) // get angle from finger tip to centre
does not take the views (or track) center into account, and that the arguments to atan2()
are the wrong way around (y comes first). It should be:
theta = atan2(finger.y - track.center.y, finger.x - track.center.x)
Another problem is that you add more and more contraints
in func constrainBall()
, without removing the previous ones.
You should keep references to the constraints and modify them instead.
Finally note that the width/height constraint for the ball should be 2*ballRadius
, not trackRadius
.
Putting it all together (and removing some unnecessary type conversions), it would look like this:
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)
}
这篇关于当我结合使用UIPanGestureRecognizer和自动布局时,我的UIViews混乱了的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!