UIPanGestureRecognizer to rotate mapView - ios

I have a method for panning the map using a UIPanGestureRecognizer however it doesn't rotate correctly (I want to be able to make circle motions that cause the camera to move in a circle), is there a better approach here?
#objc func panMap (sender: UIPanGestureRecognizer) {
if sender.state == .began {
print("began")
} else if sender.state == .changed {
// rotating map camera
let position = sender.translation(in: mapView)
let newDirection = mapView.camera.heading.advanced(by: (Double(position.x) + Double(position.y)))
let newCamera: MKMapCamera = MKMapCamera(lookingAtCenter: mapView.camera.centerCoordinate, fromDistance: mapView.camera.altitude, pitch: mapView.camera.pitch, heading: newDirection)
mapView.setCamera(newCamera, animated: false)
// Eugene Dudnyk's solution to help fix logic error when appending distance
sender.setTranslation(.zero, in: mapView)
} else if sender.state == .ended {
print("end")
}
}

The issue is that translation of pan gesture recognizer accumulates relative to the position at the start of panning, but you interpret it as accumulation since the last state change, and always add to the camera angle.
In case if user moved finger to afa.x = 160, and then moves finger back, afa.x changes let's say from 160 to 150, but you will append to the angle 150 / 30 = 5 degrees where angle has to be negative, - 10 / 30 degrees instead.
Try this, maybe it will make things better:
#objc func panMap (sender: UIPanGestureRecognizer) {
if sender.state == .began {
print("began")
} else if sender.state == .changed {
// rotating map camera
let translation = sender.translation(in: mapView)
let location = sender.location(in: mapView)
let bounds = mapView.bounds
let vector1 = CGVector(dx: location.x - bounds.midX, dy: location.y - bounds.midY)
let vector2 = CGVector(dx: vector1.dx + translation.x, dy: vector1.dy + translation.y)
let angle1 = atan2(vector1.dx, vector1.dy)
let angle2 = atan2(vector2.dx, vector2.dy)
mapView.camera.heading += (angle2 - angle1) * 180.0 / Double.pi
} else if sender.state == .ended {
print("end")
}
sender.setTranslation(.zero, in: mapView)
}

Related

Rotating SCNNode on Y Axis Based on Pan Gesture

I have a model which I rotate using the pan gesture. It works fine but it does not rotate all 360 degrees. It rotates to 180 and then stops. Also if I end the pan gesture and starts again then the position is reset. Here is my code.
#objc func panned(recognizer :UIPanGestureRecognizer) {
var newAngleY :Float = 0.0
if recognizer.state == .changed {
let sceneView = recognizer.view as! ARSCNView
let touchPoint = recognizer.location(in: sceneView)
let translation = recognizer.translation(in: sceneView)
print(translation.x)
let scnHitTestResults = self.sceneView.hitTest(touchPoint, options: nil)
if let hitTestResult = scnHitTestResults.first {
let chairNode = hitTestResult.node
newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180
newAngleY += chairNode.eulerAngles.y/180
chairNode.eulerAngles.y = newAngleY
}
}
else if recognizer.state == .ended {
currentAngleY = newAngleY
}
}
You could try this:
Create a var currentAngleY: Float = 0.0
Then in your PanGestureRecognizer try the following:
/// Rotates An Object On It's YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateObject(gesture: UIPanGestureRecognizer) {
guard let nodeToRotate = currentNode else { return }
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
nodeToRotate.eulerAngles.y = newAngleY
if(gesture.state == .ended) { currentAngleY = newAngleY }
}
nodeToRotate refers to an SCNNode I have already selected, but you should be able to adapt it as you see fit.

How do I set up an UIPanGestureRecognizer using xib file

func handlePanGesture (panGesture: UIPanGestureRecognizer) {
let translation = panGesture.translation(in: panGesture.view?.superview)
if panGesture.state == .began || panGesture.state == .changed {
panGesture.view?.center = CGPoint(x: (panGesture.view?.center.x)! + translation.x, y: (panGesture.view?.center.y)! + translation.y)
let distance: Double = sqrt(pow((Double(panGesture.view!.center.x) - Double(poin1.x)),2) + pow((Double(panGesture.view!.center.y) - Double(poin1.y)),2))
if distance <= 50{
panGesture.view!.center.x = CGFloat(poin1.x)
panGesture.view!.center.y = CGFloat(poin1.y)
}
panGesture.setTranslation(CGPoint.zero, in: self.view)
}
}
I want custom func handlePanGesture using many points to move in the gesture.

Make a pan gesture respond on vertical swipe downs

I implemented a pan gesture in my application to dismiss a view. Basically when a user swipes down, the view dismisses.
However, I want it to work so that it doesn't dismiss if a user swipes diagonally down, or semi-down. I only want it to be responsive on a strict vertical swipe down. Here is my code so far.
var originalPosition: CGPoint?
var currentPositionTouched: CGPoint?
func panGestureAction(_ panGesture: UIPanGestureRecognizer) {
if currentScreen == 0 {
let translation = panGesture.translation(in: view)
if panGesture.state == .began {
originalPosition = view.center
//currentPositionTouched = panGesture.location(in: view)
} else if panGesture.state == .changed {
view.frame.origin = CGPoint(
x: view.frame.origin.x,
y: view.frame.origin.y + translation.y
)
panGesture.setTranslation(CGPoint.zero, in: self.view)
} else if panGesture.state == .ended {
let velocity = panGesture.velocity(in: view)
if velocity.y >= 150 {
UIView.animate(withDuration: 0.2
, animations: {
self.view.frame.origin = CGPoint(
x: self.view.frame.origin.x,
y: self.view.frame.size.height
)
}, completion: { (isCompleted) in
if isCompleted {
self.dismiss(animated: false, completion: nil)
}
})
} else {
UIView.animate(withDuration: 0.2, animations: {
self.view.center = self.originalPosition!
})
}
}
}
}
You are on the right track with currentPositionTouched. In your .began block, set currentPositionTouched to the panGesture's location
currentPositionTouched = panGesture.location(in: view)
Then, in your .changed and .ended block, use a check to determine if the x value is the same and develop your logic accordingly.
if currentPositionTouched.x == panGesture.location(in: view).x
You can use gestureRecognizerShouldBegin. You can set it up to only recognize vertical gestures (i.e. those for which the angle is less than a certain size). For example, in Swift, it is:
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let gesture = gestureRecognizer as? UIPanGestureRecognizer else { return false }
let translation = gesture.translation(in: gesture.view!)
if translation.x != 0 || translation.y != 0 {
let angle = atan2(abs(translation.x), translation.y)
return angle < .pi / 8
}
return false
}
}
Just make sure to set the delegate of the pan gesture recognizer.
Note, I'm not checking to see if it's absolutely vertical (because of variations in the gesture path, it will likely rarely be perfectly vertical), but within some reasonable angle from that.
FYI, this is based upon this Objective-C rendition.

UIKit Dynamics with pan, rotate and pinch gesture

I have added UIDynamics to imageview and used pan gesture for that. It is working fine with pan gesture but when I apply pinch gesture with that it is not working. It is showing large imageview but when I start dragging then it is changed to original size.
Here is my code:
func handleAttachmentGesture(_ sender: UIPanGestureRecognizer) {
let location = sender.location(in: emojiSuperView!)
let boxLocation = sender.location(in: self)
switch sender.state {
case .began:
print("Your touch start position is \(location)")
print("Start location in image is \(boxLocation)")
animator.removeAllBehaviors()
let centerOffset = UIOffset(horizontal: boxLocation.x - self.bounds.midX, vertical: boxLocation.y - self.bounds.midY)
attachmentBehavior = UIAttachmentBehavior(item: self, offsetFromCenter: centerOffset, attachedToAnchor: location)
animator.addBehavior(attachmentBehavior)
case .ended:
print("Your touch end position is \(location)")
print("End location in image is \(boxLocation)")
animator.removeAllBehaviors()
// 1
let velocity = sender.velocity(in: emojiSuperView!)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
if magnitude > ThrowingThreshold {
// 2
let pushBehavior = UIPushBehavior(items: [self], mode: .instantaneous)
pushBehavior.pushDirection = CGVector(dx: velocity.x / 10, dy: velocity.y / 10)
pushBehavior.magnitude = magnitude / ThrowingVelocityPadding
self.pushBehavior = pushBehavior
animator.addBehavior(pushBehavior)
// 3
let angle = Int(arc4random_uniform(20)) - 10
itemBehavior = UIDynamicItemBehavior(items: [self])
itemBehavior.friction = 0.2
itemBehavior.allowsRotation = true
itemBehavior.addAngularVelocity(CGFloat(angle), for: self)
animator.addBehavior(itemBehavior)
}
default:
attachmentBehavior.anchorPoint = sender.location(in: emojiSuperView!)
break
}
}
func recognizePinchGesture(sender: UIPinchGestureRecognizer)
{
weak var dynamicItem: UIDynamicItem?
// whatever your item is, probably a UIView
dynamicItem = self
let behavior = UIGravityBehavior(items: [dynamicItem!])
let animator = UIDynamicAnimator(referenceView: emojiSuperView!)
// or however you're getting your animator
animator.addBehavior(behavior)
sender.view!.transform = sender.view!.transform.scaledBy(x: sender.scale, y: sender.scale)
animator.updateItem(usingCurrentState: self)
self.animator.updateItem(usingCurrentState: self)
sender.scale = 1
}
When a user does any transform event, save current transform to a global variable.
after that when panning start assigns new transform in began state using UIAttachmentBehavior's action property.
attachmentBehavior.action = {
self.attachmentBehavior.items[0].transform = self.aTransform
}

UIGestureRecognizer - detach SKSpriteNode after dragging

I am using UIPanGestureRecognizer to rotate my sprite nodes with one finger (i.e. dragging the sprite up rotates it clockwise, and down anti clockwise).
This is my piece of code that works for the above function
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
self.view!.addGestureRecognizer(gestureRecognizer)
handlePan(gestureRecognizer)
func handlePan(_ gestureRecognizer:UIPanGestureRecognizer){
if gestureRecognizer.state == .began {
var touchLocation = gestureRecognizer.location(in: gestureRecognizer.view)
touchLocation = self.convertPoint(fromView: touchLocation)
gestureRecognizer.minimumNumberOfTouches = 1
self.selectTouchedNode(touchLocation: touchLocation)
}
if gestureRecognizer.state == .changed {
var touchLocation = gestureRecognizer.location(in: gestureRecognizer.view)
touchLocation = self.convertPoint(fromView: touchLocation)
var translation = gestureRecognizer.translation(in: gestureRecognizer.view!)
translation = CGPoint(x: translation.x, y: translation.y)
self.rotateNode(translation: translation)
}
if gestureRecognizer.state == .ended {
selectedNode.removeAllActions()
}
}
func selectTouchedNode(touchLocation: CGPoint) {
let touchedNode = self.atPoint(touchLocation)
if touchedNode is SKSpriteNode{
selectedNode = touchedNode as! SKSpriteNode
print ("touched")
} else {
selectedNode.removeAllActions()
}
}
func rotateNode(translation: CGPoint) {
selectedNode.zRotation = translation.y * 0.01
print ("Rotate")
print (translation.y)
}
However, my sprite nodes becomes "sticky" after I drag, i.e. if i've dragged sprite1, dragging the background of the scene still rotates sprite 1 until i drag sprite2, which then rotates sprite2 until I drag another node.
How can I "detach" the nodes so that it only rotates when I start the drag on their position?

Resources