【问题标题】:my UIViews muck-up when I combine UIPanGestureRecognizer and autolayout当我结合 UIPanGestureRecognizer 和自动布局时,我的 UIViews 搞砸了
【发布时间】:2016-11-30 10:16:46
【问题描述】:

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


编辑

Martin R's answer 现在根据需要显示此内容。我唯一的额外代码更改是删除了不必要的声明 var shapeLayer = CAShapeLayer()


this example 中的数学非常有意义,直到我尝试将球和轨迹都约束到视图的中心并在运行时添加球的中心坐标作为偏移量。我关注了these recommendations on how to constrain a view

有三件事我不明白。

首先,从两个变量trackRadius 和角度theta 计算圆的周长并使用sincostheta 找到xy 坐标不会将球放在正确的位置。

其次,使用atan求视图中心与触摸点的夹角theta,使用trackRadiusthetaxy坐标不会放置或移动球沿圆周移动到一个新的地方。

第三,每当我拖动球时,调试区域中的消息会显示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)
        ])
    }
}

【问题讨论】:

    标签: ios swift uiview autolayout uipangesturerecognizer


    【解决方案1】:

    主要的错误是

    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() 中,不删除以前的。 您应该保留对约束的引用并改为修改它们。

    最后请注意,球的宽度/高度约束应该是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)
    }
    

    【讨论】:

    • 睡了一夜好觉,醒来看到这件事真是太高兴了!您完成了我尝试的所有操作,并使 Swift 代码在此过程中更易于解释。它必须是公认的答案。
    最近更新 更多