My understanding is once there is a UILongPressGestureRecognizer listener attached to something, e.g. a button, it will fire the selected function, and has 4 default parameters. numbersOfTapsRequired, touchesrequired, min press duration, and allowable movement.
Here's my code:
1) this attaches listener to all backspace buttons:
func attachBackspaceLongPressListeners() {
for button in backspaceButtons {
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(KeyboardViewController.deleteBackwardsLongPress(_:)))
button.addGestureRecognizer(longPress)
}
}
And here is the function it fires:
func deleteBackwardsLongPress(_ gestureRecognizer: UIGestureRecognizer) {
proxy.deleteBackward()
}
It works fine in the simulator, but on the actual keyboard app, it is very jerky. I found however that if after you hold the finger down, and then move it around while still holding, the function fires as expected.
Related
I have a UILongPressGestureRecognizer which is fired when there is 1 finger on the screen. However, as soon as I put 2 fingers, the function is not fired anymore and I need to create a new gesture for 2 fingers.
How to have UILongPressGestureRecognizer to accept a flexible amount of touches ?
let longScreenGesture = UILongPressGestureRecognizer(target: self, action: #selector(screenTapped(_:)))
longScreenGesture.minimumPressDuration=0.1
longScreenGesture.allowableMovement=0
longScreenGesture.numberOfTouchesRequired=1
sceneView.isMultipleTouchEnabled=true
sceneView.addGestureRecognizer(longScreenGesture)
#objc func screenTapped(_ sender: UILongPressGestureRecognizer)
{
print(sender.numberOfTouches) // -> Always displays 1
}
I ended up using TouchesBegan / Moved / Ended and analyzed gestures by myself
I am trying to understand a reproducible bug with my gesture recognisers. I have 2 recognisers on an MKMapView, one UITapGestureRecognizer and one UILongPressGestureRecogniser. Both of them work as expected the first time, however, if I use the long press (which adds an annotation to the map) the next tap gesture will return in the 'possible' state but never hit the 'recognized' state.
▿ Optional<Array<UIGestureRecognizer>>
▿ some : 2 elements
- 0 : <UITapGestureRecognizer: 0x7fda7543ebc0; state = Possible; view = <MKMapView 0x7fda78026e00>>
- 1 : <UILongPressGestureRecognizer: 0x7fda7543e8c0; state = Possible; view = <MKMapView 0x7fda78026e00>; numberOfTapsRequired = 0; minimumPressDuration = 0.2>
After I tap once, and nothing happens, a second tap will then perform the associated function i.e. make it to the recognized state.
I am intercepting all the clicks on the window and the tap definitely takes place each time but the first one after a long press never seems to become accepted. Is there something I'm missing here? The gestures are added as below:
let mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
mapView.addGestureRecognizer(mapTap)
let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(mapLongPress(_:)))
pressGesture.minimumPressDuration = 0.2
pressGesture.numberOfTouchesRequired = 1
mapView.addGestureRecognizer(pressGesture)
Could this be to do with the other gestures which are added by default on an MKMapView?
I tried using your code and got the same result.
I solved it with a tricky solution. I hope it would be helpful for you
mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
mapTap.delegate = self
mapView.addGestureRecognizer(mapTap)
pressGesture = UILongPressGestureRecognizer(target: self, action:
#selector(mapLongPress(_:)))
pressGesture.minimumPressDuration = 0.2
pressGesture.numberOfTouchesRequired = 1
mapView.addGestureRecognizer(pressGesture)
#objc func mapTapped(_ gesture: UITapGestureRecognizer) {
// your code
}
#objc func mapLongPress(_ gesture: UILongPressGestureRecognizer) {
// your code
if gesture.state == .began {
mapTap.isEnabled = false
} else if gesture.state == .cancelled || gesture.state == .ended {
mapTap.isEnabled = true
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
In your case you expect that the tap recognizer as well as the long press recognizer operate simultaneously: When you tap the view, both should start the recognition process. When you end the tap before the minimum tap time for the long press, the tap gesture should fire, but when you end the tap later, the long press gesture should fire.
But the Apple docs say:
UIKit normally allows the recognition of only one gesture at a time on
a single view. Recognizing only one gesture at a time is usually
preferable because it prevents user input from triggering more than
one action at a time. However, this default behavior can introduce
unintended side effects. For example, in a view that contains both pan
and swipe gesture recognizers, swipes are never recognized. Because
the pan gesture recognizer is continuous, it always recognizes its
gesture before the swipe gesture recognizer, which is discrete.
In your case, the long tap gesture recognizer is continuous while the tap gesture recognizer is discrete, so there could be a problem in recognizing the tap.
I would thus try to explicitly allow both recognizers to simultaneous recognice their gestures. An example how to do this is given here.
As soon as the long press recognizer fires, you could cancel the recognition operation of the tap recognizer.
Hope this helps!
I'm getting a lot of delay in an app that needs to feel more instant.
I've got a simple app that toggles left and right. It's a seesaw and when one end is up, the other is down. Your supposed to use two fingers and tap on the screen like your just fidgeting with it. It supposed to be an aid to ADHD.
I've got two large images for left and right state. I've got a gesture recognized on the image and I check the coordinates of the tap to determine if you tapped the right side to go down or the left. I'm also using AudioServicesPlayAlertSound to cause a small pop vibrate on touch begin in an effort to give a bit of a feedback stimulus to the user.
In my tests, if I tap rapidly it seems I get a backlog of taps on the toggle. The vibrations happen way after the tap is over, so it feels useless. Sometimes the UI image gets backlogged just switching between images.
override func viewDidLoad() {
super.viewDidLoad()
let imageView = Seesaw
let tapGestureRecognizer = UILongPressGestureRecognizer(target:self, action: #selector(SeesawViewController.tapped));
tapGestureRecognizer.minimumPressDuration = 0
imageView?.addGestureRecognizer(tapGestureRecognizer)
imageView?.isUserInteractionEnabled = true
}
func tapped(touch: UITapGestureRecognizer) {
if touch.state == .began {
if(vibrateOn){
AudioServicesPlaySystemSound(1520)
}
let tapLocation = touch.location(in: Seesaw)
if(tapLocation.y > Seesaw.frame.height/2){
print("Go Down")
Seesaw.image = UIImage(named:"Down Seesaw");
seesawUp = false
} else if (tapLocation.y < Seesaw.frame.height/2){
print("Go Up");
Seesaw.image = UIImage(named:"Up Seesaw");
seesawUp = true
}
}
}
Another idea - would it be faster to implement this as a button? Are gesture recognizers just slow? Are the way I'm drawing the image states consuming the wrong type processing power?
Sems like you made mistake in your code. You want to create tap recognizer, but you created UILongPressGestureRecognizer
Please change line from
let tapGestureRecognizer = UILongPressGestureRecognizer(target:self, action: #selector(SeesawViewController.tapped))
to
let tapGestureRecognizer = UITapGestureRecognizer(target:self, action: #selector(SeesawViewController.tapped))
Or you may add transparent button and put your code to its handler
// onDown will fired when user touched button(not tapped)
button.addTarget(self, action: #selector(onDown), for: .touchDown)
I am trying to determine if there is a means of programmatically setting a gesture recognizer state, to force it to begin prior to it actually detecting user input.
For example, I am adding a pan gesture recognizer to an image when a long press is detected, like so;
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
myImage.addGestureRecognizer(longPressRecognizer)
func longPressed(sender: UILongPressGestureRecognizer) {
let mainWidth = UIScreen.mainScreen().bounds.width
let mainHeight = UIScreen.mainScreen().bounds.height
let myView: UIView(frame: CGRect(x: 0, y: 0, width: mainWidth, height: mainHeight)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
myView.addGestureRecognizer(gestureRecognizer)
self.view.addSubview(myView)
}
In the handlePan() function, I'm able to determine when the pan starts and ends;
func handlePan(gesture: UIPanGestureRecognizer) {
if gesture!.state == UIGestureRecognizerState.Began {
print("Started pan")
}
if gesture!.state == UIGestureRecognizerState.Ended {
print("Ended pan")
}
}
My issue is that, to detect when the gesture started, the user has to (1) long press on the image, (2) release their finger, (3) press and hold and start panning. Ideally, I'd like to have the user (1) long press on the image, (2) start panning.
To accomplish this, I'm imagining I need to figure out a way to "trick" things into believing that the pan gesture already began.
note: In practicality, there is more complexity than what's presented here, which is why I need to add a subview with the pan gesture, rather than just adding the pan gesture to the image directly.
What you want to do is add both gesture recognizes up front, set their delegates to your class, allow them to recognize simultaneously (using the below method), and only use the data from the pan when the long press has successfully been recognized.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
I am working with xcode to create a view that allows users to drag buttons. with the code below, I can move the button to the touch and drag from there, but I cant click the button and drag.
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for obj in touches {
let touch = obj as! UITouch
let location = touch.locationInView(self.view)
word1Button.center = location
}
}
Buttons respond to touch events, so when the user touches down within the bounds of a button the view underneath will not receive those touch events. You can get around this by using a gesture recogniser on your button instead of relying on the lower level touch delivery methods. A long press gesture recognizer would probably work best:
// Where you create your button:
let longPress = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
word1Button.addGestureRecognizer(longPress)
//...
func handleLongPress(longPress: UILongPressGestureRecognizer) {
switch longPress.state {
case .Changed:
let point = longPress.locationInView(view)
button.center = point
default:
break
}
}
Note that by default, the UILongPressGestureRecognizer needs the user to hold down for 0.5 seconds before the gesture starts recognizing (and therefore starts dragging). You can change this with the minimumPressDuration property of UILongPressGestureRecognizer. Be careful not to make it too short though - as soon as the gesture recognizes it will cancel other touches to the button, preventing the button action from being fired when the touch is lifted.