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
Related
We have developed an app in iOS 12 which worked really fine. Now in iOS 13 UIPanGestureRecognizer is not working any more.
I've search for solutions but didn't find anything.
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
let center = view.frame.origin.y + view.bounds.height / 2
if(center <= SliderView.bounds.height && center >= SliderView.bounds.minY) {
view.center = CGPoint(x:view.center.x, y:view.center.y + translation.y)
}
if(center > SliderView.bounds.height) {
view.center = CGPoint(x: view.center.x, y: view.center.y - 1)
}
if(center < SliderView.bounds.minY) {
view.center = CGPoint(x: view.center.x, y: view.center.y + 1)
}
lowerSliderView.frame = CGRect(x: 0, y: center, width: SliderView.bounds.width, height: SliderView.bounds.height - center)
slider = 1 - Float(center / SliderView.bounds.height)
slider = min(slider, 1.0)
slider = max(slider, 0.0)
}
recognizer.setTranslation(CGPoint.zero, in: self.view)
}
I expect that the slider will work on the app.
I had similar issues with my gesture recognizer on iOS13. (Worked fine on 12)
My problem was: I was setting .center = someValue, on my view which had the gesture recognizer on it, but that view also had Constraints on it. iOS13 doesn't seem to like it when you have constraints on a view, and are also setting it's frame manually. So i converted my code completely to just set the frame inside the gesture recognizer's handler method. iOS13 seems to have gotten more strict with preventing you from setting .center, or .frame manually if you also have constraints on that view and are calling layoutIfNeeded() causing a layout pass. I'm not sure about this, but for now, i'm back up and running using manual frame setting.
If your gesture recognizer event is not firing, please try to implement the following method and inspect the values inside to see if there are other gesture recognizers overlaid and competing for the touch gesture. Return TRUE for your gesture recognizer, and suppress others, or just return true for all of them. You need to set the delegate first.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer is UIPanGestureRecognizer || gestureRecognizer is UIRotationGestureRecognizer) {
print("Should recognize")
return true
} else {
print("Should NOT recognize")
return false
}
}
This is the setup of mine. works fine now, after I removed all the constraints from my view which had the recognizer on it. Constraints were "undoing" the translations which I had in the gesture recognizer method, only resulting in +1 or -1 movement of the view, and then it snapped back in place.
let recStart = UIPanGestureRecognizer(target: self, action: #selector(handleStartPan))
recStart.delegate = self
self.startView.addGestureRecognizer(recStart)
And handleStartPan:
#objc final func handleStartPan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: containerOfMyView)
// do stuff with translation.x or .y
...
Apologies as I'm still learning the basics of Swift.
I'm trying to move a button when I drag it which sounds simple. I can't figure out how to pass along the sender information to the drag function so I can associate it with the button that is being dragged.
I create multiple one word buttons which are only text and attach a pan gesture recognizer to each of them:
let pan = UIPanGestureRecognizer(target: self, action: #selector(panButton(_:)))
let word = UIButton(type: .system)
word.addGestureRecognizer(pan)
I've created this function to trigger when the button is moved:
func panButton(sender: UIPanGestureRecognizer){
if sender.state == .began {
//wordButtonCenter = button.center // store old button center
} else if sender.state == .ended || sender.state == .failed || sender.state == .cancelled {
//button.center = wordButtonCenter // restore button center
} else {
let location = sender.location(in: view) // get pan location
//button.center = location // set button to where finger is
}
}
I'm getting the following error:
Use of unresolved identifier 'panButton'
First of all, your action needs to be a selector in Swift 3. So that would look something like this:
let pan = UIPanGestureRecognizer(target: self, action: #selector(panButton(_:))
Also, you can't pass the value of a button through a selector, so you would need to change your func to be:
func panButton(sender: UIPanGestureRecognizer){
...
}
If you're wondering how you are supposed to find the button if you can't pass it as a parameter, then you might consider using tags.
As #benjamin points out in Swift 3 you needs to be a selector. I've updated my code to the following in order to extract the button tag:
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(panGesture:)))
panGesture.minimumNumberOfTouches = 1
let word = UIButton(type: .system)
With the following selector:
#objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {
let buttonTag = (recognizer.view?.tag)!
if let button = view.viewWithTag(buttonTag) as? UIButton {
if recognizer.state == .began {
wordButtonCenter = button.center // store old button center
} else if recognizer.state == .ended || recognizer.state == .failed || recognizer.state == .cancelled {
button.center = wordButtonCenter // restore button center
} else {
let location = recognizer.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
}
first off, i am new to swift development. i am learning and things are going mostly smoothly but i've come across this issue and can't seem to beat it down. i searched but there just isn't a lot of info out there. the few questions relating to this issue on SO aren't helpful either (perhaps i just don't know the proper terms to search for..?)
anyway.
i have set up my gesture recognizers for swipes, taps and long presses. they work fine. the problem comes when i do the long press. the long press is used to trigger player movement (technically it moves the background image(s)) it works, he animates and moves but while moving the system does not recognize any other gesture input. the moment i let go of the long press the other recognizers function as they should.
in my GameViewController i added
skView.multipleTouchEnabled = true
but it had no affect. it still doesn't seem to register the additional touch events while the long press is active. my code is below.
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
// gesture recognizer
// swipe up
let swipeUp:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(GameScene.handleSwipe(_:)))
swipeUp.direction = .Up
view.addGestureRecognizer(swipeUp)
// swipe dn
let swipeDn:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(GameScene.handleSwipe(_:)))
swipeDn.direction = .Down
view.addGestureRecognizer(swipeDn)
// swipe rt
let swipeRt:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(GameScene.handleSwipe(_:)))
swipeRt.direction = .Right
view.addGestureRecognizer(swipeRt)
// swipe lt
let swipeLt:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(GameScene.handleSwipe(_:)))
swipeLt.direction = .Left
view.addGestureRecognizer(swipeLt)
// tap
let tapped:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(GameScene.handleTap(_:)))
view.addGestureRecognizer(tapped)
// long press
let pressed:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(GameScene.handlePress(_:)))
pressed.minimumPressDuration = 0.1
view.addGestureRecognizer(pressed)
}
// swipes
func handleSwipe(sender: UISwipeGestureRecognizer) {
switch sender.direction {
case UISwipeGestureRecognizerDirection.Right:
swipeRT = true
print("swipe RIGHT")
case UISwipeGestureRecognizerDirection.Left:
swipeLT = true
print("swipe LEFT")
case UISwipeGestureRecognizerDirection.Up:
swipeUP = true
print("swipe UP")
case UISwipeGestureRecognizerDirection.Down:
swipeDN = true
print("swipe DOWN")
default:
print("nothing to see here")
}
}
// taps
func handleTap(sender: UITapGestureRecognizer) {
print("tap")
}
func handlePress(sender: UILongPressGestureRecognizer) {
if sender.state == .Began {
theWAY()
}
if sender.state == .Ended {
player.runAction(animIdle)
moving = false
}
}
i realize that there may be better/more efficient ways of achieving this. i'm 100% open to suggestions and critique. as i said, i'm new to this whole thing. thanks for any help and i apologize if this runs afoul of the whole "search before you post" thing. i did search, i promise.
You can use the UIGestureRecognizerDelegate method gestureRecognizer(_:shouldRecognizeSimultaneouslyWithGestureRecognizer:).
See this question and answer for reference.
I'm in the process of learning swift (and spritekit) and trying to make a simple game.
In my game, the hero should jump or duck...
The hero needs to jump when the screen is tapped,or duck if the screen is tap+held (long gesture)
So basic pseudo code:
if tapped
heroJump()
else if tappedAndHeld
heroDuck()
I have a func which is seen in almost all tutorials, which handles the touch event:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self) //need location of tap for something
switch gameState {
case .Play:
//do in game stuff
break
case .GameOver:
//opps game ended
}
break
}
}
super.touchesBegan(touches, withEvent: event)
}
Is there a way, to include in this touch event, to decide if it was tapped or held? I can't seem to get my head around the fact, the program will always recognise a tap before a long gesture?!?
Anyway, in an attempt to solve my problem, I found THIS question, which introduced to me recognisers, which I tried to implement:
override func didMoveToView(view: SKView) {
// other stuff
//add long press gesture, set to start after 0.2 seconds
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
longPressRecognizer.minimumPressDuration = 0.2
self.view!.addGestureRecognizer(longPressRecognizer)
//add tap gesture
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tapped:")
self.view!.addGestureRecognizer(tapGestureRecognizer)
}
And these are the functions the gestures call:
func longPressed(sender: UILongPressGestureRecognizer)
{
if (sender.state == UIGestureRecognizerState.Ended) {
print("no longer pressing...")
} else if (sender.state == UIGestureRecognizerState.Began) {
print("started pressing")
// heroDuck()
}
}
func tapped(sender: UITapGestureRecognizer)
{
print("tapped")
// heroJump()
}
How can I combine these two things?
Can I add a way to determine whether it was tapped or hold in my touchBegins event, or can I scrap that method and use only the two functions above?
One of many problems being getting the location if using the latter?
Or maybe I'm looking at this completely wrong, and there's a simple and/or built in way in swift/spritekit?
Thanks.
You only need the UITapGestureRecognizer and the UILongPressRecognizer. You do not need to do anything with touchesBegan and touchesEnded, because the gesture recognizer analyses the touches itself.
To get the location of the touch you can call locationInView on the gesture recognizer to get the location of the gesture or locationOfTouch if you are working with multitouch gestures and need the location of each touch. Pass nil as parameter when you want the coordinates in the window’s base coordinate system.
Here is a working example:
func setupRecognizers() {
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("handleLongPress:"))
view.addGestureRecognizer(tapRecognizer)
view.addGestureRecognizer(longTapRecognizer)
}
func handleLongPress(recognizer: UIGestureRecognizer) {
if recognizer.state == .Began {
print("Duck! (location: \(recognizer.locationInView(nil))")
}
}
func handleTap(recognizer: UIGestureRecognizer) {
print("Jump! (location: \(recognizer.locationInView(nil))")
}
If a long press is recognized handleTap: tap is not called. Only if the user lifts his finger fast enough handleTap: will be called. Otherwise handleLongPress will be called. handleLongPress will only be called after the long press duration has passed. Then handleLongPress will be called twice: When the duration has passed ("Began") and after the user has lifted his finger ("Ended").
you do the same thing you are doing for longpress, wait till the .Ended event
func tapped(sender: UITapGestureRecognizer)
{
if sender.state == .Ended {
print("tapped")
}
}
A tap event will always happen, this can't be prevented because lets face it, you need to touch the screen. What should be happening though is when you enter the long press event, the tap event should go into a Cancel state instead of an Ended state
Brand new to Swift but am picking it up little by little. I have 2 objects that I want to control at the same time with UISwipeGestureRecognizer. I have it working for one object but need to be able to swipe on the left side of the screen and the right side to control the 2 objects separately. I'm guessing I can implement a statement that if swipe is less than this position control this object else control this one just not sure how to implement. This is what I'm using to control the one object.
self.swipeRightGesture.addTarget(self, action: Selector("handleRightSwipe:"))
self.swipeRightGesture.direction = .Right
self.swipeRightGesture.numberOfTouchesRequired = 1
self.view?.addGestureRecognizer(self.swipeRightGesture)
func handleRightSwipe(sender: UIGestureRecognizer) {
if !self.isMoving && self.isMovingUp == true{
self.leftobjectmoveright()
self.isMoving = true
self.isMoving = false
self.isMovingUp = false
}
}
func leftobjectmoveright() {
self.leftobject.physicsBody?.velocity = CGVectorMake(75,0)
}
And how do you intend the user to reflect which of the three gestures (up, left, and right) is for which object? Are you starting the gesture on top of the object? If that's the case, there are a couple of approaches.
The most logical approach, in my opinion, is to create a separate set of gestures for each object, and then in the gesture recognizer, you can look at gesture.view to identify which one resulted in the gesture recognizer's selector to be called. For example:
let leftObjectDownSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleDownSwipe:"))
leftObjectDownSwipe.direction = .Down
leftObject?.addGestureRecognizer(leftObjectDownSwipe)
let rightObjectDownSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleDownSwipe:"))
rightObjectDownSwipe.direction = .Down
rightObject?.addGestureRecognizer(rightObjectDownSwipe)
With a handleDownSwipe like so:
func handleDownSwipe(gesture: UISwipeGestureRecognizer) {
if gesture.view == leftObject {
println("swiped left one")
} else if gesture.view == rightObject {
println("swiped right one")
}
}
Or, if the handling of the gestures for the two different objects were sufficiently different, you might just give them completely separate gesture handlers. It's just a question of how much common code there is in these two gesture handlers.
Alternatively, you could put the gesture recognizer on the superview that contains these two view objects:
let downSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleDownSwipe:"))
downSwipe.direction = .Down
view.addGestureRecognizer(downSwipe)
And then you could have the gesture recognizer look at the width of the view and compare that to the locationInView:
func handleDownSwipe(gesture: UISwipeGestureRecognizer) {
let location = gesture.locationInView(gesture.view)
if location.x < (gesture.view!.frame.size.width / 2.0) {
println("swiped left side")
} else {
println("swiped right side")
}
}