Drag GoogleMaps-Marker via swiping over map - swift

I have a Google-Map + marker. I know how to make a marker draggable. The standard behaviour is to 'Long-Press' a marker and you can drag it.
What I want is to drag the marker by swiping over the map. It shouldn't be neccessary to hit the marker. User swipes over the map from left to right and simultanously the marker changes position from left to right where the distance equals swipe-length.
I can't find a suitable solution in the GM-API. Any ideas?
I'm using Swift 2.2
var marker: GMSMarker!
func createMarker(title: String, target: CLLocationCoordinate2D) {
marker = GMSMarker(position: target)
marker.appearAnimation = kGMSMarkerAnimationPop
marker.map = map
}
func activateDragMode() {
marker.draggable = true
map.settings.scrollGestures = false
map.settings.zoomGestures = false
map.settings.rotateGestures = false
}

The GoogleMap-API doesn't provide the method I need. But i found a solution:
map.settings.consumesGesturesInView = false
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panRecognition))
view.addGestureRecognizer(panGestureRecognizer)
func panRecognition(recognizer: UIPanGestureRecognizer) {
if marker.draggable {
let markerPosition = map.projection.pointForCoordinate(marker.position)
let translation = recognizer.translationInView(view)
recognizer.setTranslation(CGPointZero, inView: view)
let newPosition = CGPointMake(markerPosition.x + translation.x, markerPosition.y + translation.y)
marker.position = map.projection.coordinateForPoint(newPosition)
}
}
I had to deactivate 'consumesGesturesInView' to add my own PanGestureRecognizer, which manipulates the marker.

Swift 5.1
By analysis on google api and other method, I did not get the proper one. The best and sweet answer for this question is by using pan gesture.
add the pan gesture to the mapView as
self.mapView.settings.consumesGesturesInView = false
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self. panHandler(_:)))
self.mapView.addGestureRecognizer(panGesture)
Implementation of pan gesture method as
#objc private func panHandler(_ pan : UIPanGestureRecognizer){
if pan.state == .ended{
let mapSize = self.mapView.frame.size
let point = CGPoint(x: mapSize.width/2, y: mapSize.height/2)
let newCoordinate = self.mapView.projection.coordinate(for: point)
print(newCoordinate)
//do task here
}
}

Related

How can I reset the position of my marker view when the cancel button is pressed?

I have a markerView which you can grab and move around. I added a panGesture to it.
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handleDraggedView(_:)))
fanMenu.addGestureRecognizer(panGesture)
To drag the view around and update the position of it:
#objc func handleDraggedView(_ sender: UIPanGestureRecognizer){
if let marker = documentationMarkers.first(where: {$0.documentationId == documentationPressed.id}) {
if(fanMenu.isOpen && marker.documentationType == "DEFAULT") {
self.viewForTilingView.bringSubview(toFront: marker)
let translation = sender.translation(in: self.viewForTilingView)
marker.center = CGPoint(x: marker.center.x + translation.x, y: marker.center.y + translation.y)
let point = viewForTilingView.convert(marker.center, to: scrollView)
fanMenu.center = CGPoint(x: point.x,
y: point.y)
sender.setTranslation(CGPoint.zero, in: self.viewForTilingView.superview)
print("Startpoint-X: \(point.x)", "Startpoint-Y: \(point.y)")
if(sender.state == .ended) {
print("Endpoint-X: \(point.x)", "Endpoint-Y: \(point.y)")
if let documentation = documentations.first(where: {$0.id == marker.documentationId}) {
let levelZoom : CGFloat = pow(2.0, CGFloat(viewForTilingView.tileProvider!.levelsOfDetail - 1))
let positionX = Int(marker.center.x * levelZoom)
let positionY = Int(marker.center.y * levelZoom)
_ = updatePosition(documentation: documentation, positionX: positionX, positionY: positionY)
documentationAdded(doc: documentation, update: true)
}
}
}
}
}
my start and endpoints are correct. When I long press my marker it prints the correct startpoints and when I release it somewhere else it gives me the correct endpoints.
I added some buttons to my marker view. Two of them are a cancel and confirm Button.
In a switch case statement it checks which button is selected. If the conform button is selected and you moved the marker around it updates the position ( = do nothing cause it updates it anyways) but when the cancel button is selected it should not update the postion and reset it to the old one.
func contextMenuButtonClicked(buttonId: String) {
let doc = documentationPressed
switch buttonId {
case "main":
break
case "cancelButton":
//update to the old position
My question is how can I get the old position of my marker view and place it to the start position when the user presses the cancel button.
You can store the marker position by switching between pan states.
For e.g.
//declare a variable inside your class- var olPostion = CGPoint()
if sender.state == .began {
oldPosition = marker.center
}
and on cancelButtonPress, reset the button position to old position
marker.center = oldPosition
NOTE: If you want to reset the button position the position it was
when the view controller loaded, assign the value od oldPosition in
viewDidLoad()

Swift - UIPanGestureRecognizer selecting all layers when only want the top layer selected

I have function which creates a drag line to connect 2 buttons to each other. This works fine but if some buttons overlap each other, it will select both if I drag over where they overlap. I only want to connect the top button.
I think the issue is with the sender.location selecting layers on top and below. Is there a way to tell the sender.location to only select the top view? Thanks for any input and direction
func addPanReconiser(view: UIView){
let pan = UIPanGestureRecognizer(target: self, action: #selector(DesignViewController.panGestureCalled(_:)))
view.addGestureRecognizer(pan)
}
#objc func panGestureCalled(_ sender: UIPanGestureRecognizer) {
let currentPanPoint = sender.location(in: self.view)
switch sender.state {
case .began:
panGestureStartPoint = currentPanPoint
self.view.layer.addSublayer(lineShape)
case .changed:
let linePath = UIBezierPath()
linePath.move(to: panGestureStartPoint)
linePath.addLine(to: currentPanPoint)
lineShape.path = linePath.cgPath
lineShape.path = CGPath.barbell(from: panGestureStartPoint, to: currentPanPoint, barThickness: 2.0, bellRadius: 6.0)
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point) {
button.layer.borderWidth = 4
button.layer.borderColor = UIColor.blue.cgColor
} else {
button.layer.borderWidth = 0
button.layer.borderColor = UIColor.clear.cgColor
}
}
case .ended:
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
//DO my Action here
lineShape.path = nil
lineShape.removeFromSuperlayer()
}
}
default: break
}
}
}
Note: some of the lines of codes are from custom extensions. I kept them in as they were self explanatory.
Thanks for the help
There is a way to walk around. It seems like you simply want your gesture end up at one button above all the others, thus by adding a var outside the loop and each time a button picked, comparing with the var of its level at z.
case .ended:
var pickedButton: UIButton?
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
if pickedButton == nil {
pickedButton = button
} else {
if let parent = button.superView, parent.subviews.firstIndex(of: button) > parent.subviews.firstIndex(of: pickedButton!) {
pickedButton = button
}
}
}
}
//DO my Action with pickedButton here
lineShape.path = nil
lineShape.removeFromSuperlayer()
A UIView has a property called subViews where elements with higher indexes are in front of the ones with lower indexes. For instance, subView at index 1 is in front of subView with index 0.
That being said, to get the button that's on top, you should sort your buttonArray the same way subViews property of UIView is organized. Assuming that your buttons are all siblings of the same UIView (this might not be necessarily the case, but you can tweak them so you get them sorted correctly):
var buttonArray = view.subviews.compactMap { $0 as? UIButton }
Thus, keeping your buttonArray sorted that way, the button you want is the one that contains let point = sender.location(in: button) with higher index in the array.

I want to limit the number of taps

Each tap adds an object on my scene, but I want to add it only once and then disable the Tap gesture. I looked everywhere but none of them are working with my code. Can someone help me with this? Either limiting the tap to 1 only or disabling it. I tried adding the tap gesture as Outlet and then setting .isEnabled = false but it is still not working.
class ARScene: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var sceneView: ARSCNView!
var tap : UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
let configuration = ARWorldTrackingConfiguration()
self.sceneView.session.run(configuration)
// Set the view's delegate
sceneView.delegate = self
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
// let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
//sceneView.scene = scene
registerGestureRecognizer()
}
func registerGestureRecognizer(){
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
#objc func handleTap(gestureRecognizer: UIGestureRecognizer){
//let touchLocation = gestureRecognizer.location(in: sceneView)
let sceneLocation = gestureRecognizer.view as! ARSCNView
let touchLocation = gestureRecognizer.location(in: sceneLocation)
let hitResult = self.sceneView.hitTest(touchLocation, types: [.existingPlaneUsingExtent, .estimatedHorizontalPlane])
if hitResult.count > 0 {
guard let hitTestResult = hitResult.first else{
return
}
let node = SCNNode()
let scene = SCNScene(named: "art.scnassets/bucket/Bucket2.scn")
let nodeArray = scene!.rootNode.childNodes
for childNode in nodeArray{
node.addChildNode(childNode as SCNNode)
}
let worldPos = hitTestResult.worldTransform
node.scale = SCNVector3Make(0.009,0.009,0.009);
node.position = SCNVector3(x: 0, y: worldPos.columns.3.y, z: -1.4)
sceneView.scene.rootNode.addChildNode(node)
tap.isEnabled = false //NOT WORKING, I want to stop tap gesture here
}
}
In order to disable your tapGesture you need to have reference to the same gesture instance you assign tap to.
There are two ways either create global instance so you can change its properties like Enable/Disable from anywhere
Or access the variable from the action/method for that gesture.
Create the global varibale for your tap
like
var tap: UITapGestureRecognizer! // global varibale
func registerGestureRecognizer(){
tap = UITapGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
and then disable this in taphandle
tap.isEnabled = false // disable the gesture
Or
2 Update your handle methods to
#objc func handleTap(withTapRecognizer tapGesture: UITapGestureRecognizer) {
........
tapGesture.isEnabled = false // use the instance variable and disable the gesture this will disable the gesture from which its been called
}
For you to access the gesture in the function, you need to initialize it using
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:)))
Then in your handleTap method, you can this at the end of the function
gestureRecognizer.view?.removeGestureRecognizer(gestureRecognizer)

Conflict between Pan Gesture and Tap Gestures

I'm currently working on a game that uses UIGestureRecognizers. I'm using the pan gesture to move the player around and the tap gesture to detect other UI button touches. Everything seems to work fine except that there is a conflict between the 2 gestures. Whenever the player is on the move (pan gesture gets recognized) the game ignores all my tap gestures (Once the pan gesture is recognized, the view won't recognize tap gestures).
Can someone please show me how to make the 2 gestures work together. I want the player to stop moving when a UI button gets tapped. In another word, I want to cancel the pan gesture whenever a tap gesture is recognized.
Thank you so much in advance!
Here is how I setup the 2 gestures:
let singleTap = UITapGestureRecognizer(target: self, action: #selector(doSingleTap))
singleTap.numberOfTapsRequired = 1
singleTap.delegate = self
self.view?.addGestureRecognizer(singleTap)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGesture.minimumNumberOfTouches = 1
panGesture.delegate = self
self.view?.addGestureRecognizer(panGesture)
#objc func handlePan(gestureReconizer: UIPanGestureRecognizer) {
if isPaused || player.isInAction {return}
let translation = gestureReconizer.translation(in: self.view)
if gestureReconizer.state == .changed {
let angle = 180 + (atan2(-translation.x, translation.y) * (180/3.14159))
player.movementAngle = angle
player.atState = .Walk
}
if gestureReconizer.state == .ended {
player.movementAngle = 0.0
if player.atState == .Walk {
player.atState = .Idle
}
}
}
#objc func doSingleTap(gestureReconizer: UITapGestureRecognizer) {
let originaTapLocation = gestureReconizer.location(in: self.view)
let location = convertPoint(fromView: originaTapLocation)
let node = atPoint(location)
switch node.name {
case "HeroAvatar":
//do stuff here
break
case "Fire":
//do stuff here
break
case "Potion":
//do stuff here
break
default:
break
}
}
You need to implement delegate method of UIGestureRecognizerDelegate like below:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Don't recognize a single tap gesture until a pan gesture fails.
if gestureRecognizer == singleTap &&
otherGestureRecognizer == panGesture {
return true
}
return false
}
Hope this will work for you :)
For more info Apple Doc Preferring One Gesture Over Another

What is the best way to zoom and deplace nodes?

I'm working with Swift 3, SpriteKit and Xcode.
So I have a node named backgroundNode, and I attach every nodes of my game to this backgroundNode.
Now I would like to be able to zoom on my game with a pinch gesture, and when I'm zoomed in to navigate in my game.
I see 2 possibilities to do this :
deplace the background node and change its scale to zoom in and zoom out,
use SKCameraNode
What do you think is the best option ?
I already tried the first option but the zoom gesture is quite complex as if I scale the backgroundNode up when I want to zoom, the anchor point is in 0;0 and not 0.5;0.5 so it doesnt zoom where the pinch gesture is detected, but from the bottom right corner, I don't know if you see what I mean.
And for the second option, I can't manage to move the camera without having a glitchy effect, maybe my code is wrong but it really seems correct.
Can you help me ?
Edit : So I got it working using SKCameraNode and UIPanGestureRecognizer, here is the code :
var cam: SKCameraNode!
let panGesture = UIPanGestureRecognizer()
override func didMove(to view: SKView)
{
cam = SKCameraNode()
camera = cam
cam.position = CGPoint(x: playableRect.midWidth, y: playableRect.midHeight)
addChild(cam)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(GameScene.panFunction))
view.addGestureRecognizer(panGesture)
}
func panFunction(pan : UIPanGestureRecognizer)
{
let deltaX = pan.translation(in: view).x
let deltaY = pan.translation(in: view).y
cam.position.x -= deltaX
cam.position.y += deltaY
pan.setTranslation(CGPoint(x: 0, y: 0), in: view)
}
Now I'm struggling with the Zoom. I tried using UIPinchGestureRecognizer but it doesn't work as good as the pan gesture, here is what I tried :
var firstPinch: CGFloat = 0
var pinchGesture = UIPinchGestureRecognizer()
let panGesture = UIPanGestureRecognizer()
var cam: SKCameraNode!
override func didMove(to view: SKView)
{
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(GameScene.pinchFunction))
view.addGestureRecognizer(pinchGesture)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(GameScene.panFunction))
view.addGestureRecognizer(panGesture)
}
func pinchFunction(pinch : UIPinchGestureRecognizer)
{
if UIGestureRecognizerState.began == pinch.state
{
firstPinch = pinch.scale
}
actualPinch = pinch.scale
cam.xScale -= actualPinch - firstPinch
cam.yScale -= actualPinch - firstPinch
}
How would you do it ?
You need to post your code. I helped someone with this in another forum. Their code + my answer should give a general idea of what to do:
https://forums.developer.apple.com/message/192823#192823
Basically it involves UIPanGestureRecognizer etc, and then a bit of delta-scale logic to adjust for the new bounds of the camera.