I'm having trouble retrieving the current position of a view, absolutely in the iPhone screen, when the view is scaled. Here is a minimal example of what I've done.
I have a single elliptical view on screen. When I tap, it grows and start to shiver to show that it is selected. Then I can drag the view all over the screen.
Here is the code:
ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// The View
let toDrag = MyView(frame: CGRect(x: 0, y: 0, width: 50, height: 40))
toDrag.center = CGPoint(x:100, y:100)
// The gesture recognizer
let panGestRec = UIPanGestureRecognizer(target: self, action:#selector(self.panView(sender:)))
toDrag.addGestureRecognizer(panGestRec)
self.view.addSubview(toDrag)
}
func panView(sender: UIPanGestureRecognizer) {
guard let senderView = sender.view else { return }
if sender.state == .recognized { print("Pan Recognized", terminator:"")}
if sender.state == .began { print("Pan began", terminator: "") }
if sender.state == .changed { print("Pan changed", terminator: "") }
if sender.state == .ended { print("Pan ended", terminator: "") }
let point = senderView.convert(senderView.center, to: nil)
print (" point \(point)")
let translation = sender.translation(in: self.view)
senderView.center = CGPoint(x: senderView.center.x + translation.x, y: senderView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
}
MyView
import UIKit
class MyView: UIView {
var isAnimated: Bool = false
override init(frame: CGRect) {
super.init(frame: frame) // calls designated initializer
self.isOpaque = false
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.isOpaque = false
self.backgroundColor = UIColor.clear
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isAnimated = true
rotate1()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isAnimated = false
endRotation()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
isAnimated = false
endRotation()
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {return}
context.addEllipse(in: rect)
context.setLineWidth(1.0)
context.setStrokeColor(UIColor.black.cgColor)
context.setFillColor(UIColor.red.cgColor)
context.drawPath(using: .fillStroke)
}
func rotate1() {
guard isAnimated else { return }
UIView.animate(withDuration: 0.05, delay:0.0, options: [], animations: {
self.transform = CGAffineTransform.init(rotationAngle:.pi/16).scaledBy(x: 1.5, y: 1.5)
}, completion: { _ in
self.rotate2()
})
}
func rotate2() {
guard isAnimated else { return }
UIView.animate(withDuration: 0.05, delay:0.0, options: [], animations: {
self.transform = CGAffineTransform.init(rotationAngle:.pi/(-16)).scaledBy(x: 1.5, y: 1.5)
}, completion: { _ in
self.rotate1()
})
}
func endRotation () {
// End existing animation
UIView.animate(withDuration: 0.5, delay:0.0, options: .beginFromCurrentState, animations: {
self.transform = CGAffineTransform.init(rotationAngle:0 ).scaledBy(x: 1, y: 1)
}, completion: nil)
}
}
With this version, here are some displays of the position
Pan began point (233.749182687299, 195.746572421573)
Pan changed point (175.265022520054, 181.332358181369)
Pan changed point (177.265022520054, 184.332358181369)
Pan changed point (177.265022520054, 184.332358181369)
Pan changed point (178.265022520054, 185.332358181369)
Pan changed point (179.265022520054, 186.332358181369)
Pan changed point (180.265022520054, 187.332358181369)
Pan changed point (180.265022520054, 188.332358181369)
Pan changed point (181.265022520054, 188.332358181369)
Pan RecognizedPan ended point (181.265022520054, 189.332358181369)
You can see that the first position (began point) isn't correct: the view was animated at that moment. The other positions are OK, when dragging, the view don't animate.
If I comment the code in rotate1 and rotate2, all the positions are correct, so I assume that the sizing and eventually the rotation of the view are interfering in the result. My question is: how can I retrieve the correct position of the view, when it is scaled? Obviously, the line
senderView.convert(senderView.center, to: nil)
didn't make what I thought : converting the coordinate to the fixed size screen coordinates?
In fact, I had another problem with view convert (Retrieve the right position of a subView with auto-layout) and my problem was the same, so the solution is here...
I didn't use the method correctly. If I change using beninho85's solution (and even more cleaner cosyn's method to use just one view), it works! I can have the right coordinates even if the view is animated.
I just had to replace, in ViewController :
let point = senderView.convert(senderView.center, to: nil)
with
let point = senderView.convert(CGPoint(x:senderView.bounds.midX, y:senderView.bounds.midY), to: nil)