How to catch all events in Swift without breaking other event handlers - swift

How can I catch any touch up event in my application view without affecting any other event in subview or subview of the subview?
Currently whenever I add UILongPressGestureRecognizer to my root view, all other addTarget functions break in subviews and their subviews.
func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith: UIGestureRecognizer) -> Bool {
return true
}
override func viewDidLoad() {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.setTitle("Click me", for: .normal)
button.addTarget(self, action: #selector(buttonClick), for: .touchDown)
self.view.addSubview(button)
initLongTapGesture() // This kills the button click handler.
}
func initLongTapGesture () {
let globalTap = UILongPressGestureRecognizer(target: self, action: #selector(tapHandler))
globalTap.delegate = self
globalTap.minimumPressDuration = 0
self.view.addGestureRecognizer(globalTap)
}
#objc func tapHandler(gesture: UITapGestureRecognizer) {
if ([.ended, .cancelled, .failed].contains(gesture.state)) {
print("Detect global touch end / up")
}
}
#objc func buttonClick() {
print("CLICK") // Does not work when initLongTapGesture() is enabled
}

The immediate solution to allow the long press gesture to work without preventing buttons from working is to add the following line:
globalTap.cancelsTouchesInView = false
in the initLongTapGesture function. With that in place you don't need the gesture delegate method (which didn't solve the issue anyway).
The big question is why are you setting a "long" press gesture to have a minimum press duration of 0?
If your goal is to monitor all events in the app then you should override the UIApplication method sendEvent. See the following for details on how to subclass UIApplication and override sendEvent:
Issues in overwriting the send event of UIApplication in Swift programming
You can also go through these search results:
https://stackoverflow.com/search?q=%5Bios%5D%5Bswift%5D+override+UIApplication+sendEvent
Another option for monitoring all touches in a given view controller or view is to override the various touches... methods from UIResponder such as touchesBegan, touchesEnded, etc. You can override these in a view controller class, for example, to track various touch events happening within that view controller.
Unrelated but it is standard to use the .touchUpInside event instead of the touchDown event when handing button events.

Related

Can't find which view triggered the tap gesture recognizer in my custom view

I know a lot of questions are answered for the issues like this but in my case, the common answers do not work.
I have a custom view which is a small view that appears above the active textfield to show the clipboard data so if the user wants that data in that textField, taps on my custom view so the clipboard text automatically pastes into that textField.
everything works fine when there is a single textField on the page. but if I add another textField, this custom view can't detect the correct textField to paste the text into it.
here is my code inside my customView to detect current textField
private func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction))
tap.cancelsTouchesInView = true
tap.delegate = self
window?.addGestureRecognizer(tap)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.frame.contains(subviews.first!.frame) {
tapComple?(true)
} else {
tapComple?(false)
}
return true
}
I know that already other answers on the stackoverflow are saing to use this:
if touch.view == self
But it is not working in my case...
The demo project to see everything.
You need to detect if the rectangle contains the point which you touched. You can achieve that with the code below.
Change your gestureRecognizer(_:shouldRecieve:) method to the following:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if subviews.first?.frame.contains(touch.location(in: subviews.first)) == true {
print(true)
tapComple?(true)
} else {
print(false)
tapComple?(false)
}
return true
}
Result:
You don't need to do any calculations or listening gesture recognizer delegate.
Instead of that you can add tap gesture recognizer on your own view, on on the window. So all taps it receive - will be correct ones.
Also right now you're adding your view as subview to text field. This is really a bad practice and will stop this view from receiving taps.
Instead of that I suggest you adding it to to UIViewController view. To do so you need to pass two views into your LContextView: superview and anchorView on which you'll bind your constraints
private func configView(superView: UIView, anchorView: UIView, size: CGSize) {
removePreviusContexes(superView: superView)
superView.addSubview(self)
backgroundColor = .white
translatesAutoresizingMaskIntoConstraints = false
widthAnchor.constraint(equalToConstant: size.width).isActive = true
heightAnchor.constraint(equalToConstant: size.height).isActive = true
centerXAnchor.constraint(equalTo: anchorView.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: anchorView.centerYAnchor, constant: -50).isActive = true
...
private func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction))
addGestureRecognizer(tap)
}

How to single out and action a UITapGestureRecognizer for a UIView that is in the UIViewController.view that also has a UITapGestureRecognizer?

I have a UIView object that I have added a UITapGestureRecognizer. The main view also has a UITapGestureRecognizer that i use to hide the keyboard. How can i prioritise the tapGesture on the child UIView to do what i need to do? I would like, when tapping the child view to trigger the action associated with this tap, rather than the main views's gesture.
I have tried adding a double tap to the child view, to distinguish between the two gestures, but this just triggers the action for the main views's tap action.
var childView: UIView!
/**Tap screen event listener - to hide the keyboard*/
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(hideKeyBaord))
self.view.addGestureRecognizer(tapGesture)
let doubleTap = UITapGestureRecognizer.init(target: self, action: #selector(doSomethingElse))
doubleTap.numberOfTouchesRequired = 2
childView.view.addGestureRecognizer(doubleTap)
One way is to implement the shouldReceiveTouch delegate method for tapGesture:
extension YourViewController : UIGestureRecognizerDelegate {
override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gestureRecogniser == tapGesture {
// tapGesture should not receive the touch when the touch is inside the child view
let location = touch.location(in: childView)
return !childView.bounds.contains(location)
} else {
return true
}
}
}
And remember to set the delegate of tapGesture to self.

Handling Multiple GestureRecognizers

I've run into an issue understanding UIGestureRecognizers. My goal right now is to have a set of GestureRecognizers to do different tasks, for example:
override func viewDidLoad() {
mainScene = GameScene(size: self.view.bounds.size)
main = view as! SKView
mainScene.panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(shiftView(recognizer:)))
main.addGestureRecognizer(mainScene.panRecognizer)
mainScene.tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(testTap(recognizer:)))
main.addGestureRecognizer(mainScene.tapRecognizer)
mainScene.pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(zoomView(recognizer:)))
main.addGestureRecognizer(mainScene.pinchRecognizer)
This is my game View Controller where I handle actions such as panning around a map, zooming, and tapping on map tiles. But I also want to be able to move sprites with a UITapGestureRecognizer so I also created this in my GameScene:
if startGame == true{
self.startGame()
for node in (self.tempGameBoard.landShipLayer.children as? Array<landship>)! {
node.landShipInteraction = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
parentViewController.view.addGestureRecognizer(node.landShipInteraction)
}
}
The landShip in this case is representative of a sprite on screen that I would like to interact with via gesture recognizers.
My issue is that if I add this second set of recognizers, the tapping action becomes completely unresponsive. I can still zoom and pan, but the tapping behaviors I expect on my map tiles do not occur. I feel as though I am missing some understanding of how the gesture recognizers work.
Any ideas?
Thanks!
The UIGestureRecognizerDelegate has a special function managing simultaneous recognition of several gestures on the same object, that will do the trick.
1) Set your UIViewController to conform UIGestureRecognizerDelegate
2) Implement the following function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer == mainScene.panRecognizer || gestureRecognizer == mainScene.pinchRecognizer) && otherGestureRecognizer == mainScene.tapRecognizer {
return true
}
return false
}
In this particular example we allow the tap gesture to get triggered simultaneously with panning and pinching.
3) Then just assign the delegates to the pan and pinch gesture recognizers:
override func viewDidLoad() {
// your code...
// Set gesture recognizers delegates
mainScene.panRecognizer.delegate = self
mainScene.pinchRecognizer.delegate = self
}

Why is this swift button setup crashing?

I'm trying to programmatically add a button to a view along with its action method. The key thing is that the action method should be in the same file with the button so I can drop the file into other apps. When the button is tapped I want the action method to be executed but it crashes instead. It just gives EXC_BAD_ACCESS with no log output.
Here's a simplified test app with only two classes: ViewController and BlueButton:
import UIKit
class ViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let button = BlueButton(mainView: view)
button.installButton()
}
}
class BlueButton: NSObject {
var mainView: UIView
init(mainView: UIView) {
self.mainView = mainView
}
func installButton() {
let button = UIButton(frame: CGRect(x: 25, y: 100, width: 150, height: 50))
button.setTitle("Tap Me", forState: UIControlState.Normal)
button.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
button.addTarget(self, action: "someAction", forControlEvents: .TouchUpInside)
mainView.addSubview(button)
}
func someAction() {
println("this is someAction")
}
}
Back trace shows nothing helpful. It ends with objc_msgSend. I tried many different ways of rearranging the code but nothing I tried worked. I added an 'All Exceptions' breakpoint but it doesn't get hit. I put a breakpoint on the someAction method just to be sure it isn't called -- it isn't. Can somebody tell me what's going on?
Your BlueButton instance is deallocated when viewDidAppear returns because there is no longer a strong reference to it.
When the button is tapped, it tries to reference the instance, but it's been deallocated, which causes the crash.
You could resolve the issue a number of ways. The simplest would be to create a property in the ViewController class and store the BlueButton in it as long as the button is visible.

Swift: Re-activating Tap Gesture on Disclosure Indicators

I have a Swift project that has a Table View Controller with multiple Static Cell Sections. Some cells have UITextFields and others have Accessory: Disclosure Indicators. I’ve implemented the following Swift code to dismiss the keyboard when the background is tapped:
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("hideKeyboard"))
tapGesture.cancelsTouchesInView = true
tableView.addGestureRecognizer(tapGesture)
}
func hideKeyboard() {
tableView.endEditing(true)
}
This works great for releasing the keyboard on tapping the background, but it also removed the tap gesture for the Disclosure Indicators (swipe still works). Does anyone know how to re-activate the tap gesture for the Disclosure Indicators cells after implementing this hideKeyboard() function?
Keep a reference to the gesture recognizer and remove it from the view when you hide the keyboard.
class YourController: UITableViewController {
let tapGesture: UITapGestureRecognizer = {
let tg = UITapGestureRecognizer(target: self, action: "hideKeyboard")
tg.cancelsTouchesInView = true
return tg
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.addGestureRecognizer(tapGesture)
}
func hideKeyboard() {
tableView.endEditing(true)
tableView.removeGestureRecognizer(tapGesture)
}
}