Swift 3 Draggable UIButton - swift

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

Related

How do I zoom the view when the player zooms in or out?

So I just learned how to make a view zoom in and out, and I did it for my gamescene but theres just a black box right outside the view, like this
enter image description here
Here's my code that I used to allow zooming in and out, this is in my didMove function
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:)))
view.addGestureRecognizer(pinch)
and this is inside no function, just the GameScene class
#objc func handlePinch(sender: UIPinchGestureRecognizer) {
guard sender.view != nil else { return }
if sender.state == .began || sender.state == .changed {
sender.view?.transform = (sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale))!
sender.scale = 1.0
}
}
How would I change the view whenever someone zooms in or out?

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.

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

Determine Node Touched in Gesture Recognizer

I have a SpriteKit scene that can have thousands of distinct nodes in it. I am also implementing a single-tap gesture recognizer on the scene, in the hopes that I can determine which node has been touched in the scene once the gesture recognizer is triggered. Currently, my (non-working) code looks like this:
#objc func singleTap(_ sender: UIPinchGestureRecognizer) {
print("single tap gesture recognized")
if sender.numberOfTouches == 1 {
let touchPoint = sender.location(in: self.view)
let touchedNode = self.atPoint(touchPoint)
if let name = touchedNode.name
{
if name == "newMapButton"
{
print("newMapButton Touched")
} else {
print("what did you touch?")
}
}
}
}
The gesture recognizer is working. When I touch the new map button I get the "single tap gesture recognized" in the console, but nothing more. What am I doing wrong here?
In GameScene file, I created my button in didMove method like so
let btnTest = SKSpriteNode(imageNamed: "button")
btnTest.setScale(0.2)
btnTest.name = "Button"
btnTest.zPosition = 10
btnTest.position = CGPoint(x: 100, y: 200)
self.addChild(btnTest)
Adding Gesture in didMove:
let tapRec = UITapGestureRecognizer()
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
Finally implementing tappedView method
#objc func tappedView(_ sender:UITapGestureRecognizer) {
if sender.state == .ended {
var post = sender.location(in: sender.view)
post = self.convertPoint(fromView: post)
let touchNode = self.atPoint(post)
if let name = touchNode.name
{
if name == "Button"
{
print("newMapButton Touched")
} else {
print("what did you touch?")
}
}
}
}

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

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.