Programmatically made buttons conflict with viewController’s gesture recognizers - ios

Referring to my last question:
Spritekit: passing from UIButtons to buttons as SKSpriteNode
I’m working on a SpriteKit game: the user can tap and swipe on the screen to move sprites inside the scene and I added gesture recognizers in my ViewController for that.
Then I create a HUD to keep 4 buttons programmatically made with which the user could add other sprites to the scene.
I want my buttons fade and scale a little when pressed and then turn back to the original state, but it seems that they conflict with viewController’s gesture recognizers: buttons fade and scale down, but they stay in that state, don’t go back to normal state.
What can I do?
This is the Button class:
import SpriteKit
protocol ButtonDelegate: NSObjectProtocol {
func buttonClicked(sender: Button)
}
class Button: SKSpriteNode {
weak var delegate: ButtonDelegate!
var buttonTexture = SKTexture()
init(name: String) {
buttonTexture = SKTexture(imageNamed: name)
super.init(texture: buttonTexture, color: .clear, size: buttonTexture.size())
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var touchEndedCallback: (() -> Void)?
weak var currentTouch: UITouch?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isUserInteractionEnabled {
setScale(0.9)
self.alpha = 0.5
if let currentTouch = touches.first {
let touchLocation = currentTouch.location(in: self)
for node in self.nodes(at: touchLocation) {
delegate?.buttonClicked(sender: self)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(1.0)
self.alpha = 1.0
touchEndedCallback?()
print("tapped!")
}
}
This is the code I used in View Controller:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let skView = view as! SKView
skView.isMultipleTouchEnabled = false
skView.presentScene(scene)
#IBAction func didTap(_ sender: UITapGestureRecognizer) {
game.myCode
}
#IBAction func didPan(_ sender: UIPanGestureRecognizer) {
let currentPoint = sender.translation(in: self.view)
if let originalPoint = panPointReference {
if abs(currentPoint.x - originalPoint.x) > (SquareSize * 0.9) {
if sender.velocity(in: self.view).x > CGFloat(0) {
//game.myCode
panPointReference = currentPoint
} else {
//game.myCode
panPointReference = currentPoint
}
}
} else if sender.state == .began {
panPointReference = currentPoint
}
}
#IBAction func didSwipe(_ sender: UISwipeGestureRecognizer) {
//game.myCode
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive shouldReceiveTouch: UITouch) -> Bool {
if UITouch .isKind(of: Button.self) {
return false
}
return true
}
func buttonClicked(sender: Button) {
//myCode
}
}

SOLVED IT!
Ok, it was simpler than I think.
(I took a break).
- I deleted UIgestures and added them programmatically;
- in my GameScene’s touchesEnded method I created a conditional to check which one of my node has been touched.
- in View Controller I add the gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) method, to avoid conflicts with gesture recognizers attached to different view (my nodes are in two different views):
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.view != otherGestureRecognizer.view {
return false
}
return true
}
I hope this will help…

I don't think they are interfering with the inbuilt gesture recognizers of the View Controller as you are not adding any gestures of your own, you are just overriding touchesBegin and touchesEnded.
Does "tapped" get printed out?
It is possible that touchesEnd() is not getting called, try implementing touchesCancelled() as well and see if that gets called.

Related

Custom UIButton .touchDragEnter and .touchDragExit area/size?

Is it possible to customize the area from the button at which it is considered .touchDragExit (or .touchDragEnter) (out of its selectable area?)?
To be more specific, I am speaking about this situation: I tap the UIButton, the .touchDown gets called, then I start dragging my finger away from the button and at some point (some distance away) it will not select anymore (and of course I can drag back in to select...). I would like the modify that distance...
Is this even possible?
You need to overwrite the UIButton continueTracking and touchesEnded functions.
Adapting #Dean's link, the implementation would be as following (swift 4.2):
class ViewController: UIViewController {
#IBOutlet weak var button: DragButton!
override func viewDidLoad() {
super.viewDidLoad()
}
}
class DragButton: UIButton {
private let _boundsExtension: CGFloat = 0 // Adjust this as needed
override open func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let outerBounds: CGRect = bounds.insetBy(dx: CGFloat(-1 * _boundsExtension), dy: CGFloat(-1 * _boundsExtension))
let currentLocation: CGPoint = touch.location(in: self)
let previousLocation: CGPoint = touch.previousLocation(in: self)
let touchOutside: Bool = !outerBounds.contains(currentLocation)
if touchOutside {
let previousTouchInside: Bool = outerBounds.contains(previousLocation)
if previousTouchInside {
print("touchDragExit")
sendActions(for: .touchDragExit)
} else {
print("touchDragOutside")
sendActions(for: .touchDragOutside)
}
} else {
let previousTouchOutside: Bool = !outerBounds.contains(previousLocation)
if previousTouchOutside {
print("touchDragEnter")
sendActions(for: .touchDragEnter)
} else {
print("touchDragInside")
sendActions(for: .touchDragInside)
}
}
return true
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
let outerBounds: CGRect = bounds.insetBy(dx: CGFloat(-1 * _boundsExtension), dy: CGFloat(-1 * _boundsExtension))
let currentLocation: CGPoint = touch.location(in: self)
let touchInside: Bool = outerBounds.contains(currentLocation)
if touchInside {
print("touchUpInside action")
return sendActions(for: .touchUpInside)
} else {
print("touchUpOutside action")
return sendActions(for: .touchUpOutside)
}
}
}
Try changing the _boundsExtension value
The drag area is exaclty equal to the area define by bounds.
So if you want to customize the drag are simple customise the bounds of your button.

Drag UIButton without it shifting to center [Swift 3]

So I found out how to make a button draggable using the UIPanGestureRecognizer. But the only way I know how to do it is by storing and dragging the button by the center. The problem with this is if you try and drag the button from a corner, the button instantly shifts from the corner to the center. What I'm looking for is a solution that would keep my finger on a selected place while moving without instantly locking onto the center.
The code I'm currently using:
func buttonDrag(pan: UIPanGestureRecognizer) {
print("Being Dragged")
if pan.state == .began {
print("panIF")
buttonCenter = button.center // store old button center
}else {
print("panELSE")
let location = pan.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
Thanks in advance.
This can be done at least in two different ways, one using GestureRecognizer your question way and other way is subclassing the UIView and implementing the touchesBegan, touchesMoved , touchesEnded, touchesCancelled in general will work for any UIView subclass can be UIButton or UILabel or UIImageView etc...
In your way, using GestureRecognizer I make a few changes you still require a var to keep the origin CGPoint of the touch in your UIButton so we get the touch position relative to the UIButton and when the drag continue adjust the UIButton origin according to the origin touch position and the positions of the movement
Method 1 GestureRecognizer
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
var buttonOrigin : CGPoint = CGPoint(x: 0, y: 0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let gesture = UIPanGestureRecognizer(target: self, action: #selector(buttonDrag(pan:)))
self.button.addGestureRecognizer(gesture)
}
func buttonDrag(pan: UIPanGestureRecognizer) {
print("Being Dragged")
if pan.state == .began {
print("panIF")
buttonOrigin = pan.location(in: button)
}else {
print("panELSE")
let location = pan.location(in: view) // get pan location
button.frame.origin = CGPoint(x: location.x - buttonOrigin.x, y: location.y - buttonOrigin.y)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Method 2 UIView subclass in this case UIButton subclass
Use this UIButton subclass
import UIKit
class DraggableButton: UIButton {
var localTouchPosition : CGPoint?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first
self.localTouchPosition = touch?.preciseLocation(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
return
}
self.frame.origin = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
self.localTouchPosition = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
self.localTouchPosition = nil
}
}
Result
Hope this helps you

UILongPressGestureRecognizer over UITextField in swift

Is there away to use a UILongPressGestureRecognizer over a UITextField without triggering field edit while still being able to edit the textfield on a regular tap?
I have tried adding a long press gesture recognizer to the UITextField but the long press seems to only work a fraction of the time.
init(frame: CGRect, userCompany: WLUserCompany) {
super.init(frame: frame)
var textField: UITextField?
var longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.longPress(gesture:)))
textField?.addGestureRecognizer(longPress)
self.addSubview(textField!)
}
#objc func longPress(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.began {
print("Long Press")
}
}
Create subclass of UIGestureRecognizer
import UIKit.UIGestureRecognizerSubclass
class TouchGestureRecognizer: UIGestureRecognizer {
var isLongPress: Bool = false
fileprivate var startDateInterval: TimeInterval = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
state = .began
self.startDateInterval = Date().timeIntervalSince1970
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
state = .ended
self.isLongPress = (Date().timeIntervalSince1970 - self.startDateInterval) > 1.0
}
}
Add gesture recognizer to your textField
let gesture = TouchGestureRecognizer(target: self, action: #selector(ViewController.textFiledPressed(gesture:)))
self.textField?.addGestureRecognizer(gesture)
And now you can check in textFiledPressed(gesture:) function if it's long press or not
func textFiledPressed(gesture: TouchGestureRecognizer) {
switch gesture.state {
case .ended:
if gesture.isLongPress {
//Do whatever you need
} else {
self.textField?.becomeFirstResponder()
}
default: break
}
}
After looking at similar SO here and here, and experimenting myself, I don't think this is possible -- at least the way you describe your intent.
I was able to add a background view with gesture control, but the gesture conflicts with the user interaction on the text field. Wouldn't a button to the side of the textfield create a better user experience?
FWIW, here is the code to add a background UIView using a double tap. a long press gesture had the same result
#IBOutlet weak var backgroundView: UIView!
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.myTextFieldTapped))
tapGesture.numberOfTapsRequired = 2
backgroundView.addGestureRecognizer(tapGesture)
}
#objc func myTextFieldTapped() {
print("Double tapped on textField")
}
Here is an image of the storyboard:

How to detect the location of my finger through in a scrollview or webview in Swift? (using touchesMoved if possible)

I want to print the location of the user's finger on a webview.
Using this (below) doesn't work on a webview
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self.view) // even when i replace this part with UIWebview
print(position.x)
print(position.y)
}
}
I think that creating a UIPanGestureRecognizer should solve it, add the following code into viewDidLoad() method:
override func viewDidLoad() {
// ...
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(webViewtouchMoved(panGesture:)))
panGesture.delegate = self
webView.addGestureRecognizer(panGesture)
// ...
}
webViewtouchMoved(panGesture:) method:
func webViewtouchMoved(panGesture: UIPanGestureRecognizer) {
if panGesture.state == .began || panGesture.state == .changed {
let position = panGesture.location(in: view)
print(position.x)
print(position.y)
}
}
also, you should add this extension for the desired ViewController:
// change 'ViewController' to your class name:
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
hope this helped.

Function to detect if there's any touch occurring on the screen

I'm trying to create a simple function, similar to the touchesBegan, that detects if there's any touch occurring on the screen.
I've hit a brick wall trying it out myself because I'm not comfortable with UITouch class, but I really need some self made function, outside the touchesBegan default one.
I was trying to do something like this 'pseudo-code/swift'
func isTouchingTheScreen() -> Bool {
let someTouchHandleConstant: uitouch ???
if imTouchingTheScreen {
return true
} else {
return false
}
}
Do you have any hints?
PS: I know that code doesn't work, don't call that out, it was just to give you some 'image' of what I was trying to do (:
The idea
You can simply keep track of every touch begun, ended or cancelled by the user.
class GameScene: SKScene {
private var activeTouches = Set<UITouch>()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
activeTouches.unionInPlace(touches)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
activeTouches.subtractInPlace(touches)
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
if let touches = touches {
activeTouches.subtractInPlace(touches)
}
}
var isTouchingTheScreen: Bool { return !activeTouches.isEmpty }
}
Keeping activeTouches updated
As you can see I am keeping updated the activeTouches Set.
Every time a touch does begin I add it to activeTouches. And every time a touch does end or is cancelled I remove it from activeTouches.
The isTouchingTheScreen computed variable
This allows me to define the isTouchingTheScreen computed property that simply returns true when the Set contains some element.
You can implement UITapGestureRecognizer as below:
var tapGesture :UITapGestureRecognizer!
override func didMoveToNode() {
// Add UITapGestureRecognizer to view
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.touchedView(_:)))
view.addGestureRecognizer(tapGesture)
}
func touchedView(sender: UITapGestureRecognizer) {
print("view touched")
}
You could implement UITapGestureRecognizer:
// global var
var tapGesture :UITapGestureRecognizer!
override func didMoveToView() {
// Add tap gesture recognizer to view
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(GameScene.handleTap(_:)))
self.tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
func handleTap(sender: UITapGestureRecognizer) {
print("GameScene tap")
if sender.state == .Ended {
var positionInScene: CGPoint = sender.locationInView(sender.view)
positionInScene = self.scene!.convertPointFromView(positionInScene)
let touchedNode = self.nodeAtPoint(positionInScene)
if touchedNode.name != "myHero" {
print("The SKSpriteNode myHero was tapped")
}
}
}
You can find more details in Apple docs here.

Resources