Search code examples
swiftxcodeuigesturerecognizeruilongpressgesturerecogni

How to limit a process between LongPressGesture-began and LongPressGesture-ended


The requirement is simple: I want to start an activity (e.g. drawing circles on the screen) when the user touches the screen and holds down the finger. This activity shall stop immediately when the finger is lifted again. So I used the UILongPressGestureRecognizer. This produces a .began and .ended event. But the problem is that when an activity is launched based on the .began event, the .ended event will not happen as long as the activity is ongoing. My intention for the .ended event is that it shall stop the launched activity. In this way the activity execution would be limited to the time between finger touch down and lift.

I enhanced my sample code to even 2 independent UILongPressGestureRecognizers which can work simultaneously using the UIGestureRecognizerDelegate construct. The simultaneous function of the two recognizers works. However, when one of them launches the activity also the second recognizer does not receive events until the activity has ended.

How can this trivial request be realized?

Here is my sample code. The activity is simplified to 10 print statements. 1 per second. When the touch finger is lifted after 2 seconds, the loop will continue for the remaining 8 seconds.

import UIKit

class ViewController: UIViewController {
    var fingerDown = false

    @IBOutlet var MyLongPress1: UILongPressGestureRecognizer!

    @IBOutlet var MyLongPress2: UILongPressGestureRecognizer!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func handleLongPress1(_ gesture: UILongPressGestureRecognizer) {
    
        if (gesture as AnyObject).state == .began {
            print("longPress1 began")
            fingerDown = true
            myActivity()
        } else  if (gesture as AnyObject).state == .ended {
            print("longPress1 ended")
            fingerDown = false
        }
    }

    @IBAction func handleLongPress2(_ gesture: UILongPressGestureRecognizer) {
    
        if (gesture as AnyObject).state == .began {
            print("longPress2 began")
            fingerDown = true
        } else  if (gesture as AnyObject).state == .ended {
            print("longPress2 ended")
            fingerDown = false
        }
    }

    func myActivity() {
        var count = 0
        while fingerDown && count < 10 {
            sleep(1)
            count += 1
            print("in myActivity \(count)")
        }
    }
}

extension ViewController: UIGestureRecognizerDelegate {

    func gestureRecognizer(
    _ gestureRecognizer: UIGestureRecognizer,
    shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
    ) -> Bool {
        return true
    }
}

Any good advice would be appreciated!


Solution

  • You are executing synchronous code in your function.

    Instead, what you should do is create an asynchronous, cancellable operation, that starts on .began event, and cancel it on .ended event. Code for this would be too complicated to write and put here in an answer. You can instead refer a concurrently / asynchronous code tutorial like this one to properly implement what you are trying to do.


    A much simpler, but dirtier solution that will still work is to simply call your function asynchronously, so it doesn't block the main thread:

        DispatchQueue.global().async {
            self.myActivity()
        }
    

    Having an async call like this in your code is ok, but there are other problems with the code which make it not very clean. e.g. your code depends on an additional fingerDown state instead of actual state of gesture; use of sleep etc..