iOS: Detect long press without removing finger - ios

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

Related

How to cancel LongPressGestureRecognizer?

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

(swift) set long press to -= 1 but now number goes down twice

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.

Draggable UIButton/Elements in Swift?

this is my first question! I was just wondering, in Swift (specifically Swift 2, although that may go without saying!), how you create a button that the user can drag around. So for example, if it is a UIButton, the user can tap and hold it, and when they move their finger, the UIButton moves with it, and when they release it, it remains in the position that the user left it. Potentially there could be a snapping system but this is unimportant for now.
I've searched StackOverflow and found some quite interesting things, however it's all for Objective-C, and although Swift is pretty similar in some respects, I can't figure out in the slightest as to how to implement this in Swift.
It would be massively appreciated for a project that I am working on!
Thank you very much!
You can implement UIPanGestureRecognizer on your UIButton.
Wherever you create your button (viewDidLoad if using outlets):
let pan = UIPanGestureRecognizer(target: self, action: "panButton:")
button.addGestureRecognizer(pan)
This creates a new pan gesture recognizer and adds it to the button. Now, you'll want to implement the pan's action. First, you need to store the center of the button to be able to reset it when you finish panning. Add this as a view controller property:
var buttonCenter = CGPointZero
Then you implement the pan action. Note that you can use gesture recognizer states to determine when the pan starts and ends:
func panButton(pan: UIPanGestureRecognizer) {
if pan.state == .Began {
buttonCenter = button.center // store old button center
} else if pan.state == .Ended || pan.state == .Failed || pan.state == .Cancelled {
button.center = buttonCenter // restore button center
} else {
let location = pan.locationInView(view) // get pan location
button.center = location // set button to where finger is
}
}
Swift 4 & 5 Version of accepted answer:
var buttonCenter: CGPoint = .zero
viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(YourViewController.panButton(pan:)))
button.addGestureRecognizer(pan)
}
#objc func panButton(pan: UIPanGestureRecognizer) {
if pan.state == .began {
buttonCenter = button.center // store old button center
} else if pan.state == .ended || pan.state == .failed || pan.state == .cancelled {
button.center = buttonCenter // restore button center
} else {
let location = pan.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
Basically, you want to implement a touch gesture recognizer and set the button's center to the center of your press when you tap/move said button.
Here's how you'll want to do that: https://stackoverflow.com/a/31487087/5700898
Also, really cool idea!

In swift, how do I detect when a press begins and end on a UILabel?

I want to be able to detect when a press begins so I can perform an action while it is pressed only, stopping that action when press ends.
I know there is a pressesBegan function on UILabels but I am not sure how to use it and can't seem to find examples.
You can use UILongPressGestureRecongizer.
Initialize the pressGestureRecongizer in the viewDidLoad method of your viewController and add it to the label:
let pressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handlePress:")
label.addGestureRecognizer(pressGestureRecognizer)
and then you define the handlePress-function
func handlePress(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Began {
// handle start of pressing
}
else if sender.state == UIGestureRecognizerState.Ended {
// handle end of pressing
}
}

UIGestureRecognizerState.Ended is not called in some cases

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.

Resources