Swift/Spritekit UILongPressGestureRecognizer seems to ignore all other gestures while Pressed - swift

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.

Related

How would I make a UiPanGestureRecognizer check if the finger is in the buttons frame?

I am trying to make an app where the user could drag a finger on top of multiple buttons and get some actions for each button.
There was a similar question from a while back but when I tried to use CGRectContainsPoint(button.frame, point) it said it was replaced with button.frame.contains(point) but this didn’t seem to work for me. Here is a link to the Photo
What I have done so far:
var buttonArray:NSMutableArray!
override func viewDidLoad()
{
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureMethod(_:)))
a1.addGestureRecognizer(panGesture)
a2.addGestureRecognizer(panGesture)
}
#objc func panGestureMethod(_ gesture: UIPanGestureRecognizer) {
if gesture.state == UIGestureRecognizer.State.began {
buttonArray = NSMutableArray()
}
let pointInView = gesture.location(in: gesture.view)
if !buttonArray.contains(a1!) && a1.frame.contains(pointInView) {
buttonArray.add(a1!)
a1Tapped(a1)
}
else if !buttonArray.contains(a2!) && a2.frame.contains(pointInView) {
buttonArray.add(a2!)
a2Tapped(a2)
}
The code did run fine but when I tried to activate the drag nothing happened. Any tips?
You want behavior similar to the system keyboard I assume? CGRectContainsPoint is not deprecated: See the docs. In Swift it's written like frame.contains().
When dealing with rects and points you have to make sure both are translated to the same coordinate system first. To do so you can use the convert to/from methods on UIView: See (the docs).
In your case smth. like the following should work (first translate button frames, then check if the point is inside):
func touchedButtonForGestureRecognizer(_ gesture: UIPanGestureRecognizer) -> UIView? {
let pointInView = gesture.location(in: gesture.view)
for (button in buttonArray) {
let rect = gesture.view.convert(button.frame from:button.superview)
if (rect.contains(pointInView)) {
return button
}
}
return nil
}

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

Swift 3 Draggable UIButton

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
}
}
}

Swift Game - Tap and Tap + Hold Gestures?

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

Swift - Programming UISwipeGestureRecognizer to separately control 2 objects

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")
}
}