Pan gesture: why need setTranslation to zero? - ios

I add a pan gesture to a view, move the view while finger moved, but I found if I do not call recognizer.setTranslation(CGPointZero, inView: self.view), translation is not right. why ?
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
recognizer.view!.center = CGPoint(x:recognizer.view!.center.x + translation.x,
y:recognizer.view!.center.y + translation.y)
recognizer.setTranslation(CGPointZero, inView: self.view)// this line must need, why?
...
}

I don't speak English well, but I think it may be enough to explain this.
A translation in UIPanGestureRecognizer stands for a vector from where you started dragging to your current finger location, though the origin of this vector is {0, 0}. So all you need to determine the distance you dragged is another point of this vector. You get this point by calling :
recognizer.translationInView(self.view)
Then this point helped you setting a new location of your view.
But UIPanGestureRecognizer is indeed a "continuous" reporter, she will not forget the state after the last report. she didn't know that you have used up that part of translation(to re-locate your view), so the next time when "handlePan" is called, the translation is not calculated from previous location of your finger , it is from the original place where started your finger dragging!!
That's why you have to call:
recognizer.setTranslation(CGPointZero, inView: self.view)
every-time you used that translation to re-locate your view, as if you are telling the recognizer that you are going to start a new drag gesture.

Related

How do I alter the touch location when handling a pinch and a pan gesture recognizer at the same time?

I'm trying to recreate an interaction similar to the photos app where you can pinch and pan a photo at the same time. Adding or removing a touch mid pan works perfectly.
In my code I'm using the location of touch to move the view. When I drag with two fingers, the pan gesture recognizers puts the point between the two fingers (as it should), but when I lift a finger it changes the point to that of that one finger, causing the view to jerk to a new position.
Setting the maximumNumberOfTouches to 1 does not solve my problem since you can touch with finger1, pan, touch with finger 2, pan, lift finger 1 and the view will jerk to the position of finger 2. Plus, I want to allow 2 finger panning since they can pinch to zoom and rotate the image as well.
I also cannot use UIScrollView for this for other reasons, but I know it doesn't have that problem.
The only solution I can think of is to get the initial touch location, then every time a finger is added or removed, offset the new location based on the old location. But I'm not sure how to get that information.
Is there an API for this? Is the above way the only way, and if so, how do I do it?
As I understand it, the issue is that your code for responding to a pan (drag) doesn't work if the user changes the number of fingers in mid-drag, because the gesture recognizer's location(in:) jumps.
The problem is that the entire basic assumption underlying your code is wrong. To make a view draggable, you do not check the location(in:). You check the translation(in:). That's what it's for.
This is the standard pattern for making a view draggable with a pan gesture recognizer:
#objc func dragging(_ p : UIPanGestureRecognizer) {
let v = p.view!
switch p.state {
case .began, .changed:
let delta = p.translation(in:v.superview)
var c = v.center
c.x += delta.x; c.y += delta.y
v.center = c
p.setTranslation(.zero, in: v.superview)
default: break
}
}
That works fine even if the user starts with multiple fingers and lifts some during the drag.
Ok, so here's how I solved it.
Inside the gesture function I have a global variable being given the touch location.
self.touchInView.x = sender.location(in: superview).x - frame.origin.x
self.touchInView.y = sender.location(in: superview).y - frame.origin.y
self.touchInParent = sender.location(in: superview)
In state == .began I have a variable called OriginalTouch which I set the location of touch.
if gesture.state == .began {
originalTouch = self.touchInView
}
Then in state == .changed I detect if the number of touches changed and calculate the offset:
//Reset original touch position if number of touch changes so view remains in the same position
if sender.numberOfTouches != lastNumberOfTouches {
originalTouch.x += (touchInView.x - originalTouch.x)
originalTouch.y += (touchInView.y - originalTouch.y)
}
lastNumberOfTouches = sender.numberOfTouches
Now I can set the view's location based on the originalTouch
self.frame.origin = touchInParent - originalTouch

SceneKit – pan gesture is moving node too quickly

I'm currently trying to use a pan gesture recognizer to move a node in SceneKit. I'm only moving it along the X-axis, however my gesture moves the object a lot further/faster then it should even when only using small gestures. I'm not 100% sure what I'm doing wrong here but here's the code for my gesture recognizer:
#objc func handlePan(_ pan:UIPanGestureRecognizer) {
if pan.state == .changed {
let translation = pan.translation(in: pan.view!)
node!.position = SCNVector3(x:node!.position.x + Float(translation.x), y:node!.position.y, z:node!.position.z)
pan.setTranslation(CGPoint.zero, in: pan.view!)
}
}
As I say the object is being moved it's just being launched at incredible speed and distance. The effect almost appears cumulative.
I thought this could be the case if I didn't reset the translation of my pan gesture recognizer, but I am doing that here
pan.setTranslation(CGPoint.zero, in: pan.view!)
I'm actually trying to get this work in an ARKit scenario, but I've stripped all that out to just get a node moving correctly but I'm still having issues.
The pan is added to an ARSCNView whereas the node I'm trying to manipulate is added as a childNode to the ARSCNView.scene.rootNode so I'm wondering if it's the positions/coordinates of these that are the problem.
let translation = pan.translation(in: pan.view!)
This code returns CGPoint with gesture position in the view in points (which is could be pixels). But SCNNode position (in real world) is position in meters. So, when you're adding one point for X position in SCNVector, you're actually adding one meter for that.
To convert screen point into 3D world coordinates use unprojectPoint method of ARSCNView. You probably will need to save previous gesture position to be able to find position changes.

Resize a UIView from only the ends using a UIPanGestureRecognizer

I have a vertical line (UIView) which I need to resize. I currently do it using a pinch gesture but now I need to do it using a swipe gesture and increase or decrease the height from the side I swipe from. So for example, if I swipe down from the TOP END of the line, the line must decrease in size only from the top. The bottom must stay anchored to the original position.
How do I do this?
I also need to be able to move the image around, which I already have implemented using a UIPanGestureRecognizer as well.
Here's what I've playing around with:
I have a small UIView on top end of the vertical line and one on the bottom end of the line. I use these as markers. Now, I'm able to move the top marker up and down. I need to resize the line to the distance between the two markers AND keep the bottom end of the line at the same position as the bottom marker.
This is what I have
func draggedViewForTopMarker(sender: UIPanGestureRecognizer) {
//To move the top marker
var translation = sender.translationInView(self.view)
sender.view!.center = CGPointMake(sender.view!.center.x, sender.view!.center.y + translation.y)
sender.setTranslation(CGPointZero, inView: self.view)
//Find distance between the markers
var distanceBetweenMarkers = (sender.view!.center.y + translation.y) - self.bottomMarker.center.y
print(distanceBetweenMarkers)
if (distanceBetweenMarkers < 0) {
distanceBetweenMarkers = distanceBetweenMarkers*(-1)
}
//**TRYING** to resize the line to have the same height as the distance between the two markers AND make sure its positioned between the markers and the bottom end of the line is still in the same place as it was originally.
var newFrame = CGRectMake(sender.view!.center.x, distanceBetweenMarkers/2, vertical.frame.width, distanceBetweenMarkers)
vertical.frame = newFrame
}
I'm open to different approaches too, or a solution to the problem I have with this approach!
You may check out at Photo Measures Lite on the App Store to better understand what I mean when I say "resize the line from one side only".
Thanks so much!
instead of making a new frame you can just adjust y position of the line to be the same as the top marker, and the height to be the same as the distance between the two markers
func draggedViewForTopMarker(sender: UIPanGestureRecognizer) {
//To move the top marker
var translation = sender.translationInView(self.view)
sender.view!.center = CGPointMake(sender.view!.center.x, sender.view!.center.y + translation.y)
sender.setTranslation(CGPointZero, inView: self.view)
//Find distance between the markers
var distanceBetweenMarkers = self.topMarker.frame.origin.y - self.bottomMarker.frame.origin.y
vertical.frame.origin.y = self.topMarker.frame.origin.y
vertical.frame.size.height = distanceBetweenMarkers
}
But this will only work if the two markers and the vertical line are the subviews of the same superview

Swift: Pan UIImage using constraints

I have an UIImage in my viewController that I am using the UIPanGesture on. I am currently using the following code to move it around based on the RayWenderlich tutorial.
#IBAction func panImage(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(self.view)
if let view = sender.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
exitButton1.center = CGPoint(x:exitButton1.center.x + translation.x, y:exitButton1.center.y + translation.y)
}
sender.setTranslation(CGPointZero, inView: self.view)
}
I am using auto layout for this app and have been informed that moving the UIImage with autoLayoutConstraints should be done instead. I changed to the following code to move the image, however, the image is now jumping all over the screen.
let translation = sender.translationInView(self.view)
image1ConstraintX.constant = image1ConstraintX.constant + translation.x
image1ConstraintY.constant = image1ConstraintY.constant + translation.y
Is there a better way of moving the image using the constraints? Can the first method be used and then the constraints updated afterwards based on the final position? And how would the second method of moving the image look if done correctly?
Generally speaking, if a view has active auto layout constraints you should not set its frame directly. This is because your change will get overwritten the next time the layout engine makes a pass over the relevant views, and you cannot control when that will happen.
Your solution to update the constant of the relevant constraints is the correct one. If you find yourself doing this a lot, you may want to write a method that takes a CGPoint and a view, and updates the relevant constraints.
Can the first method be used and then the constraints updated afterwards based on the final position?
Yes, but you probably don't want to. To accomplish this, you would remove or disable the constraints, modify frame as the user pans, and once the user is done panning, set the constant on each constraint, and re-enable the layout constraints. This would be more complex than is necessary.

How to relatively move number of objects by moving one of them with Swift?

I have a question.
I have 4 Image View objects is my storyboard. Those represents "corners". I'd like to have this option, when you move one of corners, and other three moves relatively.
Like on animated gif here
What I do now is applying Pan Gesture Recognizer to Image Views in storyboard. Then I add code to ViewController:
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
recognizer.view!.center = CGPoint(x:recognizer.view!.center.x + translation.x,
y:recognizer.view!.center.y + translation.y)
recognizer.setTranslation(CGPointZero, inView: self.view)
}
But I'm not sure what to do next. I need to recognise which corner is tapped and moving, so I could apply special relation moving function for other corners.
Heyo,
One technique is to find a vector representing the pan gesture, and then using that vector to update the rest of the image views. For instance, UIPanGestureRecognizers extends from UIGestureRecognizer, which has the method:
func locationOfTouch(_ touchIndex: Int, inView view: UIView?) -> CGPoint
The pseudocode would go somewhat like this:
Get 1 imageView's last known location as a CGPoint
Detect the pan gesture
Retrieve the location of the touch, as a CGPoint
Now that you have two CGPoints, 1 representing the start position and 1 representing the end position, you can subtract the two points to form a vector with a length and direction. Using this information, you can come up with a formula to displace the other 3 imageViews.

Resources