2016-11-30 12 views
5

Chciałbym piłkę do śledzenia palcem jak przeciągnąć go wzdłuż trajektorii kołowej dla każdego dopuszczalnego orientacji urządzenia na iPhone lub iPad. Widoki wydają się być prawidłowo wycentrowane, gdy urządzenie jest obracane, ale kula nie pozostaje na obwodzie i wydaje się, że porusza się w dowolnym miejscu podczas przeciągania.mój UIViews syf-up kiedy łączę UIPanGestureRecognizer i autoLayout


EDIT

Martin R's answer wyświetla teraz to za konieczne. Moja jedyna dodatkowa zmiana kodu było usunięcie niepotrzebnych deklaracji var shapeLayer = CAShapeLayer()

enter image description here


matematyki w this example ma sens dopóki nie próbowałem ograniczać zarówno piłki i trajektorię do centrum widoku i dodanie centrum piłkę na współrzędne jako przesunięcia w czasie wykonywania. Poszłam za these recommendations on how to constrain a view.

Są trzy rzeczy, których nie rozumiem.

pierwsze, obliczanie obwodu okręgu jest z dwóch zmiennych trackRadius i kąt theta i korzystania sin i cos o theta znaleźć x i y współrzędne nie umieści piłkę w dobrym położeniu.

Po drugie, korzystając atan znaleźć kąt theta pomiędzy centrum i punktu widzenia dotknął, a przy użyciu trackRadius z theta aby znaleźć x i y współrzędne nie będzie miejsca lub przenieść piłkę do nowego miejsca wzdłuż obwodu.

Po trzecie, za każdym razem, gdy przeciągam piłkę, wiadomość w obszarze debugowania mówi, że Xcode is Unable to simultaneously satisfy constraints, mimo że nie zgłoszono żadnych problemów z ograniczeniami przed jej przeciągnięciem.

Tutaj może występować więcej niż jeden problem. Mój mózg zaczyna boleć i byłbym wdzięczny, gdyby ktoś mógł wskazać, co zrobiłem źle.

Oto mój kod.

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) 
     ]) 
    } 
} 

Odpowiedz

4

Głównym błędu jest to, że

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

nie bierze poglądy (lub ścieżki) centrum pod uwagę i że argumenty atan2() są źle odwrotnie (y jest na pierwszym miejscu) . Powinno być:

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

Innym problemem jest to, że coraz więcej dodać contraints w func constrainBall(), bez usuwania poprzednich. Powinieneś zachować odniesienia do wiązań i modyfikować je zamiast tego.

Na koniec należy zauważyć, że ograniczenie szerokości/wysokości piłki powinno być 2*ballRadius, a nie trackRadius.

Kładzenie to wszystko razem (i usunięcie niepotrzebnych typ konwersji), to będzie wyglądać następująco:

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) 
} 
+1

po dobrze przespanej nocy, co za radość budzi się do tego! Przybiliście wszystko, co próbowałem, i uczyniłem kod Swift bardziej zrozumiałym w tym procesie. To musi być zaakceptowana odpowiedź. – Greg

Powiązane problemy