当我结合使用UIPanGestureRecognizer和自动布局时,我的UIViews混乱了 [英] my UIViews muck-up when I combine UIPanGestureRecognizer and autolayout

查看:133
本文介绍了当我结合使用UIPanGestureRecognizer和自动布局时,我的UIViews混乱了的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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






编辑



现在






这个例子非常有意义,直到我尝试将球和轨迹约束到视图的中心并在运行时将球的中心坐标添加为偏移。我按照这些关于如何约束视图的建议



有三件事我不明白。



首先,从两个变量 trackRadius 和角度 theta 并使用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆