Drag gesture recognizer for iOS/macOS development (with initial location)

blur casual cellphone close up

Photo by Porapak Apichodilok on Pexels.com

As everybody knows, when using a mouse on a computer a drag operation occurs when the mouse button is pressed, then the device is moved, and eventually the button is released. It’s a continuous and precise operation.

In iOS we have (less precise) touches instead, but sometimes we do want to react to a drag operation similarly, upon beginning, moving, and eventually ending touches.

UIPanGestureRecognizer does a pretty good job to identify such drag operations itself, except that when it reaches its began state, the user has already moved the touch(es) a bit and we don’t know the initial location anymore – there’s no locationInViewWhenTouchesBegan property – while we’d need it to accurately compute coordinate deltas for responding to the drag operation.

(In a way that is expected, because we don’t want the pan operation to begin immediately when the first touch begins – as we are waiting to see if the user wants to actually pan or do something else.)

Update: While this is annoying, it is the way NSPanGestureRecognizer works with mouse events in macOS Cocoa development too, just that it is very difficult to see the behavior there; to catch the issue, you’d need to start dragging with the mouse from a limit of the draggable area of an element, towards the space outside that element.

To solve this problem (at least today) we need to specialize the recognizer class ourselves, reading and preserving the location in view when the touches actually begin, and use the specialization instead. Note below how we define an initial-location-in-view property for this purpose. (You would need to make the class and property public if you would want to use them outside a framework, though.)

import UIKit.UIGestureRecognizerSubclass
class DraggingPanGestureRecognizer: UIPanGestureRecognizer {
    var locationInViewWhenDraggingStarted = CGPoint.zero
    override func touchesBegan(_ touches: Set, with event: UIEvent) {
        super.touchesBegan(touches, with: event) 
        locationInViewWhenDragStarted = location(in: view)
    }
}

Finally, here is the client code showing how we can use the new service:

@IBAction func pan(_ recognizer: DraggingPanGestureRecognizer) {
    switch recognizer.state {
    case .began:
        let point = recognizer.locationInViewWhenDraggingStarted
        beginDragging(at: point)
    case .changed:
        let point = recognizer.location(in: recognizer.view)
        continueDragging(to: point)
    ...
    }
}

And/or if you need the similar implementation for macOS development (instead), i.e. inheriting from NSPanGestureRecognizer, here we go again (but note that the client code is exactly the same as above, so I won’t repeat that part below):

import AppKit
class DraggingPanGestureRecognizer: NSPanGestureRecognizer {
    var locationInViewWhenDraggingStarted = NSPoint.zero
    override func mouseDown(with event: NSEvent) {
        super.mouseDown(with: event)
        locationInViewWhenDraggingStarted = location(in: view)
    }
}

 

Advertisements

About Sorin Dolha

My passion is software development, but I also like physics.
This entry was posted in iOS and tagged , , , , , , , . Bookmark the permalink.

Add a reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s