When I drag the view and let it go in a new location .Ended is called everytime.
When I drag the view for a while and let it go in the same location it started, nothing is called, I expect .Ended or default to be called but doesn't happen.
Why does this happen? How can I learn when the user let go of the view consistently?
var gestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("dragged:"))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
func dragged(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case UIGestureRecognizerState.Began:
print("calls this everytime touch began")
case UIGestureRecognizerState.Ended:
print("doesn't call this everytime")
default:
print("never calls this")
}
}
You should almost always use UIGestureRecognizerState.Ended || UIGestureRecognizerState.Cancelled as one of the two will definitely be called at the end of a gesture. This way you can also handle cases where the user has dragged past the screen.
This happened to me when I had a case for .recognized in my switch statement (don't do this).
In my case this happened only when I used two-finger pinch or rotate gestures in the Simulator, using the option key on the keyboard.
Usually pressing the option key once more after the gesture has ended (without moving the mouse) seems to resolve the issue and the .ended state is reached.
I did not encounter this problem on an actual device.
Related
I have a slider below webView. What will be shown on the webView depends on slider's value. I am sliding and it works OK. I want to prevent my app, or more precisely my webView, doing anything while my finger is on slider(while I am moving a slider's circle). Then, after I finish sliding(remove my finger out of the slider's circle), app/webView should be able to do other things. Is this possible?
#IBAction func sliderButton(_ sender: Any) {
loadWebView()
}
sliderButton() method triggers when user is sliding, but I want loadWebView() method to be called only after user stops sliding.
For slider, you can do something like this,
sliderButton.addTarget(self, action: #selector(onSliderValChanged(slider:event:)), for: .valueChanged)
#objc func onSliderValChanged(slider: UISlider, event: UIEvent) {
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
// handle drag began
print("handle drag began")
break
case .moved:
// handle drag moved
print("handle drag moved")
break
case .ended:
// handle drag ended
print("handle drag ended")
//do for `WKWebView`'s user interaction
break
default:
break
}
}
}
Edit: it seems the swipe gesture can only takes one direction a time now. If someone knows another way to handle multiple directions at once, I’d still appreciate information!
Eidt: I find a way to deal with multiple directions concisely in this [answer] (https://stackoverflow.com/a/46104997/9645644) It uses an array literal with forEach loop. It’s much more convenient than adding gestures and dragging actions separately from storyboard.
I’m trying to get swift swipe gestures to work, everything’s fine until I tried to detect the direction of the swipe. Below is my code. I don’t understand why this isn’t working and would appreciate help!
In a view controller’s viewDidLoad I set up and added the swipe gesture recognizer, with direction[.left, .right]. After that I implemented the handler method which needs to detect the direction of the swipe. There’s no other stuff in this view controller.
After it failed to work(no response when swipe), I added a few prints, and got the output in the title.
override func viewDidLoad() {
super.viewDidLoad()
let swipeGestureRecognizer = UISwipeGestureRecognizer(target:self, action:#selector(swipeHandler(recognizer: )))
swipeGestureRecognizer.direction = [.left, .right]
view.addGestureRecognizer(swipeGestureRecognizer)
}
#objc func swipeHandler (recognizer: UISwipeGestureRecognizer){
switch recognizer.state{
case .ended:
let direction = recognizer.direction
print(direction)
if direction == .left {print(”left”)}
else if direction == .right {print(“right”)}
else {print(print(“none”)}
defaul: break
}
No matter left or right I swipe, it always prints “none”. And the direction print always give a “UISwipeGestureRecognizerDirection(rawValue: 3)”
The direction property tells the gesture when to trigger. for example if direction == .right then the swipe will trigger only on a swipe to the right. (It does not tell you the direction detected)
You need to detect one direction at a time. I would also suggest to add a method to control each swipe direction. For example.
func setUpGestures() {
// Gesture that define a left swipe.
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(Scene.swipeLeft))
swipeLeft.direction = .left
view?.addGestureRecognizer(swipeLeft)
// Do the same for the rest of the directions.
}
#objc func swipeLeft() {
// Do something when the user swipe left.
}
Hope it helps!
I have a view that has a LongPressGestureRecognizer assigned to it which calls the following method:
#IBAction func longPressOnView1Recognized(_ sender: UIGestureRecognizer) {
if sender.state == .began {
// this runs when user's finger is down a "long time"
}
if sender.state == .ended {
// this runs when user's finger goes up again after the .began state
}
}
This all works as expected, but I'm trying to find a (good/proper) way of being able to programmatically cancel the long press recognizer (in certain circumstances) while the user's finger is still down.
That is, while a user's finger is still down on the view, and the recognizer has entered the .began state, (but before the user has lifted their finger -- before recognizer enters the .ended state)... is there some code we can run that will prevent the method above from firing when the user lifts their finger... like prematurely telling IOS to no longer listen for UP events for the remainder of this gesture?
I've read these docs, but I don't have that much experience with IOS touch, and I can't seem to find any method that is designed for this purpose.
my GestureRecognizer.reset() does not seem to do what I'm describing.
I can think of two possibilities:
1) A boolean flag, that would go inside the if sender.state == .ended {} closure
2) this:
myLongPressRecognizer.isEnabled = false
myLongPressRecognizer.isEnabled = true
Both of these work but seem not so great.
You are all good with disabling and reenabling the gesture recognizer so doing
myLongPressRecognizer.isEnabled = false
myLongPressRecognizer.isEnabled = true
is completely correct.
What I am worried about is you don't completely understand gesture recognizers. You should always use switch statement when handling gesture recognizer. Check the comments:
func handleLongPressGestureRecognizer(_ sender: UIGestureRecognizer) {
switch sender.state {
case .began:
// This will be called only once when the gesture starts
print("Long press did begin at \(sender.location(in: sender.view))")
case .changed:
// This will be called whenever your finger moves (at some frequency obviously).
// At this point your long press gesture is acting exactly the same as pan gesture
print("Long press changed position to \(sender.location(in: sender.view))")
case .ended:
// This is when user lifts his finger assuming the gesture was not canceled
print("Long press ended at \(sender.location(in: sender.view))")
case .cancelled:
// This is equally important as .ended case. You gesture may be canceled for many reasons like a system gesture overriding it. Make sure to implement logic here as well.
print("Long press canceled at \(sender.location(in: sender.view))")
case .failed, .possible:
// These 2 have been added additionally at some point. Useless as far I am concerned.
break
}
}
So at least you should handle cancelled status. But also note that the changed status will be triggered whenever the gesture is moved.
You have your solution on hand already. Toggling the UILongPressGestureRecognizer isEnabled is the best way to go. Setting the state property isn't possible, because it's a get-only property.
open var state: UIGestureRecognizer.State { get } // the current state of the gesture recognizer
isEnabled property is documented as:
default is YES. disabled gesture recognizers will not receive touches. when changed to NO the gesture recognizer will be cancelled if it's currently recognizing a gesture.
You can import the gesture recognizer header:
import UIKit.UIGestureRecognizer
That will make the state property a readwrite property. Thus, to cancel the gesture, just change its state to .cancelled.
So, for example, you can cancel the long press gesture recognizer one second after it was recognized with something like:
weak var timer: Timer?
#objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
print("began")
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
gesture.state = .cancelled
}
case .ended, .cancelled:
print(gesture.state == .ended ? "Ended" : "Cancelled")
timer?.invalidate()
default:
break
}
}
So I have my long press recognizer set as follows:
#IBAction func p1s1scoreLongTouch(_ sender: AnyObject) {
p1s1currentscore -= 1
p1s1scoreoutlet.setTitle(String(p1s1currentscore), for: UIControlState.normal)
}
But now when you long press the button it changes the p1s1current score down two points, once when the duration of the long press is met, and another when lifting finger. How do I remedy this problem?
You are not taking into account the fact that your gesture recognizer action handler can (and almost surely will) be called multiple times in the course of a single gesture. You need to look at the gesture recognizer's state and decide whether you want to perform your decrement for this particular state. (The gesture recognizer is arriving as sender, but you are just ignoring it — foolishly.)
Thanks for the help guys. After learning I was using the wrong syntax I did some research and my updated code looks more like
#IBAction func p1s1scoreLongTouch(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
print("UIGestureRecognizerStateBegan")
p1s1currentscore -= 1
p1s1scoreoutlet.setTitle(String(p1s1currentscore), for: UIControlState.normal)
}
else if sender.state == .ended {
print("UIGestureRecognizerStateEnded")
}
}
Still not sure if this is the correct syntax but I know it is more correct since it works.
I am using a UILongPressGestureRecognizer. I am wondering if anyone has ever figured out a way to trigger when the minimumPressDuration has been reached without having to lift their fingers. Basically, can we trigger the end of the gesture without having to remove our fingers? Can we just use how much time had passed?
Thanks,
Collin
That's what the UIGestureStateBegan is for:
-(void)handleGesture:(UILongPressGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateBegan){
NSLog(#"minimum duration elapsed");
}else if(sender.state == UIGestureRecognizerStateEnded){
NSLog(#"user lifted their finger");
}
}
.began state ends when user lifts the finger, so the accepted answer is not correct for this problem. After some time I figured it out.
Answer in Swift:
var gesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:)))
func handleLongPress(){
//do something
gesture.addTarget(self, action: #selector(longPress(_:)))
}
#objc func longPress(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
handleLongPress()
gesture.removeTarget(self, action: #selector(longPress(_:)))
}
}
This will cause the gesture to stop tracking touch after .began state is received and re-enable it after you finish doing whatever you want to do after long press
This is very old question but I hope someone will find it useful