I'm working on a game using SpriteKit and I want to move some sprites around but I need to move only one sprite at a time. How can I use pan gesture recognizer in SpriteKit? I tried with the normal way and I got some errors so I thought maybe it has a special way.
To add a pan gesture recognizer in your game, add the gesture recognizer in your GameScene's didMove method. Then add a new function (handlePanFrom, below) in your GameScene file which will be called when the gesture is recognized.
override func didMove(to view: SKView) {
// Create the gesture recognizer for panning
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanFrom))
// Add the gesture recognizer to the scene's view
self.view!.addGestureRecognizer(panGestureRecognizer)
}
#objc func handlePanFrom(_ recognizer: UIPanGestureRecognizer) {
// This function is called when a pan gesture is recognized. Respond with code here.
}
Related
I am trying to understand a reproducible bug with my gesture recognisers. I have 2 recognisers on an MKMapView, one UITapGestureRecognizer and one UILongPressGestureRecogniser. Both of them work as expected the first time, however, if I use the long press (which adds an annotation to the map) the next tap gesture will return in the 'possible' state but never hit the 'recognized' state.
▿ Optional<Array<UIGestureRecognizer>>
▿ some : 2 elements
- 0 : <UITapGestureRecognizer: 0x7fda7543ebc0; state = Possible; view = <MKMapView 0x7fda78026e00>>
- 1 : <UILongPressGestureRecognizer: 0x7fda7543e8c0; state = Possible; view = <MKMapView 0x7fda78026e00>; numberOfTapsRequired = 0; minimumPressDuration = 0.2>
After I tap once, and nothing happens, a second tap will then perform the associated function i.e. make it to the recognized state.
I am intercepting all the clicks on the window and the tap definitely takes place each time but the first one after a long press never seems to become accepted. Is there something I'm missing here? The gestures are added as below:
let mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
mapView.addGestureRecognizer(mapTap)
let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(mapLongPress(_:)))
pressGesture.minimumPressDuration = 0.2
pressGesture.numberOfTouchesRequired = 1
mapView.addGestureRecognizer(pressGesture)
Could this be to do with the other gestures which are added by default on an MKMapView?
I tried using your code and got the same result.
I solved it with a tricky solution. I hope it would be helpful for you
mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
mapTap.delegate = self
mapView.addGestureRecognizer(mapTap)
pressGesture = UILongPressGestureRecognizer(target: self, action:
#selector(mapLongPress(_:)))
pressGesture.minimumPressDuration = 0.2
pressGesture.numberOfTouchesRequired = 1
mapView.addGestureRecognizer(pressGesture)
#objc func mapTapped(_ gesture: UITapGestureRecognizer) {
// your code
}
#objc func mapLongPress(_ gesture: UILongPressGestureRecognizer) {
// your code
if gesture.state == .began {
mapTap.isEnabled = false
} else if gesture.state == .cancelled || gesture.state == .ended {
mapTap.isEnabled = true
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
In your case you expect that the tap recognizer as well as the long press recognizer operate simultaneously: When you tap the view, both should start the recognition process. When you end the tap before the minimum tap time for the long press, the tap gesture should fire, but when you end the tap later, the long press gesture should fire.
But the Apple docs say:
UIKit normally allows the recognition of only one gesture at a time on
a single view. Recognizing only one gesture at a time is usually
preferable because it prevents user input from triggering more than
one action at a time. However, this default behavior can introduce
unintended side effects. For example, in a view that contains both pan
and swipe gesture recognizers, swipes are never recognized. Because
the pan gesture recognizer is continuous, it always recognizes its
gesture before the swipe gesture recognizer, which is discrete.
In your case, the long tap gesture recognizer is continuous while the tap gesture recognizer is discrete, so there could be a problem in recognizing the tap.
I would thus try to explicitly allow both recognizers to simultaneous recognice their gestures. An example how to do this is given here.
As soon as the long press recognizer fires, you could cancel the recognition operation of the tap recognizer.
Hope this helps!
I have the following case. parentView has it's own gestureRecognizerAand has a subview subView which has it's own UITapGestureRecognizer.
Is there any way to tell parentView that it should pass the touch events recognized in gestureRecognizerA to subView if these touch events are in subView's bounds?
gestureRecognizerA is very specific. It is a custom gesture recognizer for recognizing a circlular motion. This recognition should happen on all areas of parentView. However, when that same gesture recognizer recognizes a tap, it should pass that tap to subView.
You can easily identify the points of tap.
As for example you have a tap gesture in parent class as:
let tapGR = UITapGestureRecognizer(target: self, action: #selector(tapped))
view.addGestureRecognizer(tapGR)
#objc func tapped(gr:UITapGestureRecognizer) {
let loc:CGPoint = gr.location(in: gr.view)
//insert your touch based code here
}
Inside the tapped method you can identify the location where tap happened, so after checking bounds of the subview with location of tap you can verify is the tap happened inside the bounds of subview or not.
It seems like you just want both of those gesture recognizers to work simultaneously. Just implement UIGestureRecognizerDelegate for your parentView and make it tapGestureRecognizer's and gestureRecognizerA's delegate. Then implement an optional method there:
// MARK: - UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) {
return true
}
That might be able to detect a tap in subView even while doing a circular motion within parentView.
UPDATE: When using gesture recognizers, "forwarding touches" would be to simply calling a method of another recognizer. Just put a recognizer which is doing the forwarding as its parameter.
For instance, tapGestureRecognizer fires viewWasTapped(_ sender: UITapGestureRecognizer) when a tap is detected. Now, when your gestureRecognizerA wants to forward its events to tapGestureRecognizer, it simply does so by calling:
subView.viewWasTapped(self.gestureRecognizerA)
With an obvious change to the method itself:
func viewWasTapped(_ sender: UIGestureRecognizer) {
// ...
}
This works for UITapGestureRecognizer. The sender can be any other UIGestureRecognizer and you'd still have almost all the information to resolve a tap gesture there.
I want to start pan gesture in 1 item. And at the instant the gesture is recognized I create a new item view at the exact same spot and transfer the pan gesture to the new item so the new hovering item moves with the fingers.
How can I do this?
Here is the code so far:
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(useItemPanGesture(_:)))
item.addGestureRecognizer(panGesture)
func useItemPanGesture(panGesture: UIPanGestureRecognizer) {
let newView = createView()
newView.addPanGesture(target: self, action: #selector(self.actualPanGesture(panGesture)))
panGesture.enabled = false
}
func actualPanGesture(panGesture: UIPanGestureRecognizer) {
//do pan gesture stuff
}
I have trouble in the transfer of the pan gesture. Line starting with newView.addPangesture.
The goal is to implement two pan gestures against the same SCNScene: one with one finger and the other with two fingers.
This code below isn't working. The one-finger pan function never gets invoked even though the gesture is assigned a distinct selector. One-finger pans and two-finger pans both invoke sceneViewPannedTwoFingers.
From reading other questions it seemed like shouldRecognizeSimultaneouslyWithGestureRecognizer might be the answer, but these pans are not happening simultaneously. It should be either a one-finger pan or a two-finger pan, never both at once.
Is it possible to have two pan gestures as described above? If so, what's the right way to do this?
// Handle one-finger pans
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(sceneViewPannedOneFinger))
sceneView.addGestureRecognizer(panRecognizer)
// Handle two-finger pans
let twoFingerPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(sceneViewPannedTwoFingers))
sceneView.addGestureRecognizer(twoFingerPanRecognizer)
func sceneViewPannedTwoFingers(sender: UIPanGestureRecognizer) {
print("two finger pan!!!")
}
func sceneViewPannedOneFinger(sender: UIPanGestureRecognizer) {
print("one finger pan!!!")
}
You need to add UIGestureRecognizerDelegate to your view controller and set the gesture recognizers delegate to self inside your view controller. Add the shouldRecognizeSimultaneouslyWithGestureRecognizer method returning true for them. Make sure you set minimum and maximum number of touches also for them.
code like this
class MyClass : UIGestureRecognizerDelegate --< Delegate
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool
{
return true
}
In my ViewController.swift, I have an array that contains custom UIViews. Every time one is created, I add a UIPanGestureRecognizer to it like this:
var panRecognizer = UIPanGestureRecognizer(target: self, action: "detectPan:")
newCard.gestureRecognizers = [panRecognizer]
This links to my detectPan(recognizer: UIPanGestureRecognizer) function, which handles movement. However, since I have multiple objects linked to the function, I'm not sure how I determine from which one the input is coming.
Is there anything like (the nonexistent) recognizer.target that I could use? Should I just handle the panning from within each of the custom UIViews instead?
Any help would be appreciated!
First of all, you should declare your panRecognizer with let.
let panRecognizer = UIPanGestureRecognizer(target: self, action: "detectPan:")
Second of all, you should not set the gestureRecognizers property of any UIView. This is bad practice because UIKit may have already added its own gesture recognizers to that view behind the scenes. If you subsequently remove those recognizers by assigning [panRecognizer] to that property, you may get unexpected behavior. To add your pan gesture recognizer, do this:
newCard.addGestureRecognizer(panRecognizer)
Then, in your detectPan(recognizer: UIPanGestureRecognizer) method you can detect which UIView was panned with the following code:
func detectPan(recognizer: UIPanGestureRecognizer) {
switch recognizer.view {
case self.customViewArray[0]:
// do something
case self.customViewArray[1]:
// do something else
case ... :
// ...
}