Swift: Long Press Gesture Recognizer - Detect taps and Long Press - ios

I want to wire an action such that if the gesture is a tap, it does animates an object in a particular way but if the press duration was more than .5 secs it does something else.
Right now, I just have the animation hooked up. I don't know how I can differentiate between a long press and a tap?
How do I access the press duration to achieve the above?
#IBAction func tapOrHold(sender: AnyObject) {
UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: {
self.polyRotate.transform = CGAffineTransformMakeRotation(1/3 * CGFloat(M_PI * 2))
})
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: {
self.polyRotate.transform = CGAffineTransformMakeRotation(2/3 * CGFloat(M_PI * 2))
})
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: {
self.polyRotate.transform = CGAffineTransformMakeRotation(3/3 * CGFloat(M_PI * 2))
})
}, completion: { (Bool) in
let vc : AnyObject! = self.storyboard?.instantiateViewControllerWithIdentifier("NextView")
self.showViewController(vc as UIViewController, sender: vc)
})

Define two IBActions and set one Gesture Recognizer to each of them. This way you can perform two different actions for each gesture.
You can set each Gesture Recognizer to different IBActions in the interface builder.
#IBAction func tapped(sender: UITapGestureRecognizer)
{
println("tapped")
//Your animation code.
}
#IBAction func longPressed(sender: UILongPressGestureRecognizer)
{
println("longpressed")
//Different code
}
Through code without interface builder
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tapped:")
self.view.addGestureRecognizer(tapGestureRecognizer)
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
self.view.addGestureRecognizer(longPressRecognizer)
func tapped(sender: UITapGestureRecognizer)
{
println("tapped")
}
func longPressed(sender: UILongPressGestureRecognizer)
{
println("longpressed")
}
Swift 5
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
self.view.addGestureRecognizer(tapGestureRecognizer)
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressed))
self.view.addGestureRecognizer(longPressRecognizer)
#objc func tapped(sender: UITapGestureRecognizer){
print("tapped")
}
#objc func longPressed(sender: UILongPressGestureRecognizer) {
print("longpressed")
}

For swift2
let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
lpgr.minimumPressDuration = 0.5
lpgr.delaysTouchesBegan = true
lpgr.delegate = self
self.featuredCouponColView.addGestureRecognizer(lpgr)
Action
//MARK: - UILongPressGestureRecognizer Action -
func handleLongPress(gestureReconizer: UILongPressGestureRecognizer) {
if gestureReconizer.state != UIGestureRecognizerState.Ended {
//When lognpress is start or running
}
else {
//When lognpress is finish
}
}
For Swift 4.2/ Swift 5
let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
lpgr.minimumPressDuration = 0.5
lpgr.delaysTouchesBegan = true
lpgr.delegate = self
self.colVw.addGestureRecognizer(lpgr)
//MARK: - UILongPressGestureRecognizer Action -
#objc func handleLongPress(gestureReconizer: UILongPressGestureRecognizer) {
if gestureReconizer.state != UIGestureRecognizer.State.ended {
//When lognpress is start or running
}
else {
//When lognpress is finish
}
}

Through code without interface builder
// Global variables declaration
var longPressed = false
var selectedRow = 0
override func viewDidLoad() {
super.viewDidLoad()
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(ContactListTableViewController.handleLongPress(_:)))
longPressGesture.minimumPressDuration = 1.0 // 1 second press
longPressGesture.allowableMovement = 15 // 15 points
longPressGesture.delegate = self
self.tableView.addGestureRecognizer(longPressGesture)
}
// Long tap work goes here !!
if (longPressed == true) {
if(tableView.cellForRowAtIndexPath(indexPath)?.accessoryType == .Checkmark){
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = .None
self.selectedRow -= 1
if(self.selectedRow == 0){
self.longPressed = false
}
} else {
self.selectedRow += 1
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = .Checkmark
}
} else if(self.selectedRow == 0) {
// Single tape work goes here !!
}
But the only problem is the long press gesture runs two times. If you have found any solution do comment below !

Swift 5 using interface builder
for the normal tap you can simply create a touch up inside action from your button.
for the long press, create an outlet for your button, create the tap gesture recognizer and set it to the button then create the selector method to perform the long press tasks.
#IBOutlet var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(addToListButtonLongPress(_:)))
longPressRecognizer.numberOfTouchesRequired = 1
longPressRecognizer.allowableMovement = 10
longPressRecognizer.minimumPressDuration = 0.5
myButton.addGestureRecognizer(longPressRecognizer)
}
// Connected to myButton in interface builder.
#IBAction func myButtonTapped(_ sender: UIButton) {
print("button tapped")
}
#objc func myButtonLongPressed(_ sender: UILongPressGestureRecognizer) {
print("button long pressed")
}

Related

Not able to add UITapGestureRecognizer and UILongPressGestureRecognizer to a UIButton

I am using the following code snippet to add the gesture recogniser :
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(attachImage))
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress))
tapGesture.numberOfTapsRequired = 1
cell.image.addGestureRecognizer(tapGesture)
cell.image.addGestureRecognizer(longGesture)
#objc func longPress(_ btn : UIButton) {
selectedImageIndex = btn.tag
}
#objc func attachImage(_ btn : UIButton) {
selectedImageIndex = btn.tag
}
I am getting the following error when I press the button
unrecognized selector sent to instance 0x2802ec000
Change the functions as follows
#objc func longPress(_ sender : UILongPressGestureRecognizer) {
if let btn = sender.view {
selectedImageIndex = btn.tag
}
}
#objc func attachImage(_ sender : UITapGestureRecognizer) {
if let btn = sender.view {
selectedImageIndex = btn.tag
}
}
And change gesture initialization as follows
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(attachImage(_:)))
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:)))
If you add gesture on imageView then you have to enable isUserInteractionEnabled.
imageView.isUserInteractionEnabled = true
And If you add gesture on UIButton then
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
let tapGesture = UITapGestureRecognizer(target: self, #selector (tapGestureActionHandler(_:))) //Tap function will call when user tap on button
let longGesture = UILongPressGestureRecognizer(target: self, #selector(longGestureActionHandler(_:))) //Long function will call when user long press on button.
tapGesture.numberOfTapsRequired = 1
button.addGestureRecognizer(tapGesture)
button.addGestureRecognizer(longGesture)
}
#objc func tapGestureActionHandler(_ gesture: UITapGestureRecognizer) {
print("Tap happend")
}
#objc func longGestureActionHandler(_ gesture: UILongPressGestureRecognizer) {
print("Long press")
}

How to detect finger location in function

I want to use double click. I have written function doubleTap. How to recognize location of finger?
override func viewDidLoad()
{
super.viewDidLoad()
let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTap")
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTap()
{
}
You can use location(ofTouch:in:) to get the location of the touch. However, you need access to the gesture recognizer from inside the function where you want to access the location, so you should declare doubleTap as an instance property of your class.
class YourViewController: UIViewController {
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(YourViewController.doubleTap))
override func viewDidLoad(){
super.viewDidLoad()
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTap(){
let touchLocation = doubleTap.location(ofTouch: numberOfTouches-1,in: nil)
}
}
Change the input parameters to the function if you want to change whether you need to get the first or last touch's location or if you want to get the location relative to a subview.
You can get the gesture recognizer as a parameter
override func viewDidLoad()
{
super.viewDidLoad()
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapFunc))
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTapFunc(_ sender: UITapGestureRecognizer)
{
// Use 'sender' here to get the location
if sender.state == .ended {
// handling code
let location = sender.location(ofTouch: 0, in: self.view!)
}
}

Button click is not working with UITapGestureRecognizer when i tapped first time on the button in swift

I have implement UITapGestureRecognizer and UILongPressGestureRecognizer on button click. My problem is when i tapped first time on button its not working but second time when i press the button its start working.
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTap(_:)))
longGesture.minimumPressDuration = 0.5
button.addGestureRecognizer(longGesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(normalTap(_:)))
tapGesture.delegate = self
tapGesture.numberOfTouchesRequired = 1
tapGesture.numberOfTapsRequired = 1
button.addGestureRecognizer(tapGesture)
#objc func longTap(_ sender: UIGestureRecognizer){
print("long tap")
}
#objc func normalTap(_ sender: UIGestureRecognizer){
print("Normal tap")
}
Try this
class ViewController: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTap(_:)))
longGesture.minimumPressDuration = 0.5
button.addGestureRecognizer(longGesture)
}
#IBAction func buttonClicked(_ sender: Any) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(normalTap(_:)))
tapGesture.delegate = self
tapGesture.numberOfTouchesRequired = 1
tapGesture.numberOfTapsRequired = 1
button.addGestureRecognizer(tapGesture)
}
#objc func longTap(_ sender: UIGestureRecognizer){
print("long tap")
}
#objc func normalTap(_ sender: UIGestureRecognizer){
print("Normal tap")
}
}

Press-and-hold button for "repeat fire"

I have referred to countless other questions about a press-and-hold button but there aren't many related to Swift. I have one function connected to a button using the touchUpInside event:
#IBAction func singleFire(sender: AnyObject){
//code
}
...and another function that is meant to call the function above repeatedly while the same button is held down, and stop when the button is no longer pressed:
#IBAction func speedFire(sender: AnyObject){
button.addTarget(self, action: "buttonDown:", forControlEvents: .TouchDown)
button.addTarget(self, action: "buttonUp:", forControlEvents: .TouchUpOutside)
func buttonDown(sender: AnyObject){
timer = NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "singleFire", userInfo: nil, repeats: true)
}
func buttonUp(sender: AnyObject){
timer.invalidate()
}
}
I'm not sure what I'm doing wrong, and I don't know how to setup touch events to the same button for a different function.
You want rapid repeat fire when your button is held down.
Your buttonDown and buttonUp methods need to be defined at the top level, and not inside of another function. For demonstration purposes, it is clearer to forgo wiring up #IBActions from the Storyboard and just set up the button in viewDidLoad:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
var timer: Timer?
var speedAmmo = 20
#objc func buttonDown(_ sender: UIButton) {
singleFire()
timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(rapidFire), userInfo: nil, repeats: true)
}
#objc func buttonUp(_ sender: UIButton) {
timer?.invalidate()
}
func singleFire() {
print("bang!")
}
#objc func rapidFire() {
if speedAmmo > 0 {
speedAmmo -= 1
print("bang!")
} else {
print("out of speed ammo, dude!")
timer?.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// These could be added in the Storyboard instead if you mark
// buttonDown and buttonUp with #IBAction
button.addTarget(self, action: #selector(buttonDown), for: .touchDown)
button.addTarget(self, action: #selector(buttonUp), for: [.touchUpInside, .touchUpOutside])
}
}
Also, I changed .touchUpOutside to [.touchUpInside, .touchUpOutside] (to catch both touch up events) and call singleFire on the initial buttonDown for single fire. With these changes, pressing the button fires immediately, and then fires every 0.3 seconds for as long as the button is held down.
The button can be wired up in the Storyboard instead of setting it up in viewDidLoad. In this case, add #IBAction to buttonDown and buttonUp. Then Control-click on your button in the Storyboard and drag from the circle next to Touch Down to func buttonDown, and drag from the circles next to Touch Up Inside and Touch Up Outside to func buttonUp.
In my original answer, I answered the question of how to have a button recognize both a tap and a long press. In the clarified question, it appears you want this button to continuously "fire" as long as the user holds their finger down. If that's the case, only one gesture recognizer is needed.
For example, in Interface Builder, drag a long press gesture recognizer from the object library onto the button and then set the "Min Duration" to zero:
Then you can control-drag from the long press gesture recognizer to your code in the assistant editor and add an #IBAction to handle the long press:
weak var timer: Timer?
#IBAction func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
self.handleTimer(timer)
}
} else if gesture.state == .ended || gesture.state == .cancelled {
timer?.invalidate()
}
}
func handleTimer(_ timer: Timer) {
print("bang")
}
Or, if you also want to stop firing when the user drags their finger off of the button, add a check for the location of the gesture:
#IBAction func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
self.handleTimer(timer)
}
} else if gesture.state == .ended || gesture.state == .cancelled || (gesture.state == .changed && !gesture.view!.bounds.contains(gesture.location(in: gesture.view))) {
timer?.invalidate()
}
}
My original answer, answering the different question of how to recognize both taps and long presses on a button, is below:
Personally, I'd use tap and long press gesture recognizers, e.g.:
override func viewDidLoad() {
super.viewDidLoad()
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
button.addGestureRecognizer(longPress)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
tap.shouldRequireFailure(of: longPress)
button.addGestureRecognizer(tap)
}
#objc func handleTap(_ gesture: UITapGestureRecognizer) {
print("tap")
}
#objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .Began {
print("long press")
}
}
If you want, with the long press gesture, you could perform your action upon .Ended, too. It just depends upon the desired UX.
FYI, you can also add these two gesture recognizers right in Interface Builder, too, (just drag the respective gestures from the object library on to the button and then control-drag from the gesture recognizer to #IBAction functions) but it was easier to illustrate what's going on by showing it programmatically.
I took a different approach when coming up with my own solution. I created a UIButton subclass and enclosed all the solution inside. The difference is that instead of using an IBAction for the handler I created a property in the UIButton
class RapidFireButton: UIButton {
private var rapidFireTimer: Timer?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
init() {
super.init(frame: .zero)
commonInit()
}
private func commonInit() {
addTarget(self, action: #selector(touchDownHandler), for: .touchDown)
addTarget(self, action: #selector(touchUpHandler), for: .touchUpOutside)
addTarget(self, action: #selector(touchUpHandler), for: .touchUpInside)
}
#objc private func touchDownHandler() {
rapidFireTimer?.invalidate()
rapidFireTimer = nil
tapHandler(self)
rapidFireTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [unowned self] (timer) in
self.tapHandler(self)
})
}
#objc private func touchUpHandler() {
rapidFireTimer?.invalidate()
rapidFireTimer = nil
}
var tapHandler: (RapidFireButton) -> Void = { button in
}
}
Usage is basically creating an outlet for the button and implementing the handler like so
rapidFireButton.tapHandler = { button in
//do stuff
}
I updated #vacawama example codes to swift 3. Thanks.
#IBOutlet var button: UIButton!
var timer: Timer!
var speedAmmo = 100
#IBAction func buttonDown(sender: AnyObject) {
singleFire()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:#selector(rapidFire), userInfo: nil, repeats: true)
}
#IBAction func buttonUp(sender: AnyObject) {
timer.invalidate()
}
func singleFire() {
if speedAmmo > 0 {
speedAmmo -= 1
print("bang!")
} else {
print("out of speed ammo, dude!")
timer.invalidate()
}
}
func rapidFire() {
if speedAmmo > 0 {
speedAmmo -= 1
print("bang!")
} else {
print("out of speed ammo, dude!")
timer.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action:#selector(buttonDown(sender:)), for: .touchDown)
button.addTarget(self, action:#selector(buttonUp(sender:)), for: [.touchUpInside, .touchUpOutside])
}
Swift 5+
Based on rob's answer there is a nicer way to do this now.
Add the long press gesture recognizer by dragging it on-top of the button in the storyboard and then ...
Then you can control-drag from the long press gesture recognizer to
your code in the assistant editor and add an #IBAction to handle the
long press:
- Quote from Rob's Answer
The difference is in the code which is listed below:
var timer: Timer?
#IBAction func downButtonLongPressHandler(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: {_ in
self.downButtonPressedLogic()
self.doCalculations()
})
} else if sender.state == .ended || sender.state == .cancelled {
print("FINISHED UP LONG PRESS")
timer?.invalidate()
timer = nil
}
}
You no longer need to use NSTimer, you can just use Timer now and you can just put the code for the timer in the block which is much more compact and no need for selectors.
In my case I had another function that handles the logic for what to do when the downButton was pressed, but you can put your code you want to handle in there.
You can control the speed of the repeat fire by changing the withTimeInterval parameter value.
You can change the timing for the timer to start by finding the longPressGesture in your storyboard and changing it's Min Duration value. I usually set mine at 0.5 so that your normal button press actions can still work (unless you don't care about that). If you set it to 0 this will ALWAYS override your normal button press actions.
You can use Timer
follow this example on github
https://github.com/sadeghgoo/RunCodeWhenUIbuttonIsHeld
viewDidLoad
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.touchHoldView(sender:)))
longPressGesture.minimumPressDuration = 2
myUIView.addGestureRecognizer(longPressGesture)
#objc func touchHoldView(sender: UITapGestureRecognizer) {
if sender.state == .began {
//code..
}
}

Swift UIGestureRecognizer Touchdown

Is it possible to capture touchdown and longpress with UIGestureRecognizer?
func gestureSetup() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tapButton:")
tapTempoButton.addGestureRecognizer(tapGestureRecognizer)
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
longPressRecognizer.minimumPressDuration = 1.0
tapTempoButton.addGestureRecognizer(longPressRecognizer)
}
func tapButton(sender: UITapGestureRecognizer) {
// On touch down update UI and play sound
}
example: Gestures on UITextLabel
//Called on Label
let rotationGesture = UIRotationGestureRecognizer(target: self, action: "handleRotation:")
rotationGesture.delegate = self
let tapToDeleteTextGesture = UITapGestureRecognizer(target: self, action: "tapToDeleteText:")
tapToDeleteTextGesture.numberOfTapsRequired = 2
textLabel?.addGestureRecognizer(rotationGesture)
textLabel?.addGestureRecognizer(tapToDeleteTextGesture)
func handleRotation(recognizer: UIRotationGestureRecognizer){
let state = recognizer.state
if (state == UIGestureRecognizerState.Began || state == UIGestureRecognizerState.Changed){
if let view = recognizer.view {
view.transform = CGAffineTransformRotate(view.transform, recognizer.rotation)
recognizer.rotation = 0
}
}
}
func tapToDeleteText(recognizer: UITapGestureRecognizer){
if let view = recognizer.view {
view.removeFromSuperview()
}
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Add add the longPressGesture and you'll be set.

Resources