Catching double click in Cocoa OSX
Asked Answered
P

7

19

NSResponder seems to have no mouse double click event. Is there an easy way to catch a double click?

Pigheaded answered 2/2, 2010 at 0:15 Comment(0)
C
34

The mouseDown: and mouseUp: methods take an NSEvent object as an argument with information about the clicks, including the clickCount.

Cotton answered 2/2, 2010 at 0:34 Comment(1)
It worked. But we should take into account that it broke selecting flow of the several components - NSTextView for example.Liba
R
9

The NSEvents generated for mouseDown: and mouseUp: have a property called clickCount. Check if it's two to determine if a double click has occurred.

Sample implementation:

- (void)mouseDown:(NSEvent *)event {
    if (event.clickCount == 2) {
        NSLog(@"Double click!");
    }
}

Just place that in your NSResponder (such as an NSView) subclass.

Reportage answered 20/10, 2013 at 18:13 Comment(2)
And what would you typically do if you need to handle both - single-click and double-click. Obviously you will have to ignore single-click somehow if in the end it turns out to be double-click?Baronetcy
@Baronetcy - Off the top of my head, I can't think of any place where the action done by a single click shouldn't be done if it turns out that a double click was necessary. In text fields, a single click puts your cursor at a specific spot. Double clicking highlights that entire word. So moving the cursor on the single click was harmless. In RTS games (at least Blizzard's) a single click selects a single unit while double clicking selects all units of that type. If you want the double click to do something entirely different from what a single click does, you may want to rethink your whole UX.Reportage
B
9

The problem with plain clickCount solution is that double click is considered simply as two single clicks. I mean you still get the single click. And if you want to react differently to that single click, you need something on top of mere click counting. Here's what I've ended up with (in Swift):

private var _doubleClickTimer: NSTimer?

// a way to ignore first click when listening for double click
override func mouseDown(theEvent: NSEvent) {
    if theEvent.clickCount > 1 {
        _doubleClickTimer!.invalidate()
        onDoubleClick(theEvent)
    } else if theEvent.clickCount == 1 { // can be 0 - if delay was big between down and up
        _doubleClickTimer = NSTimer.scheduledTimerWithTimeInterval(
            0.3, // NSEvent.doubleClickInterval() - too long
            target: self,
            selector: "onDoubleClickTimeout:",
            userInfo: theEvent,
            repeats: false
        )
    }
}


func onDoubleClickTimeout(timer: NSTimer) {
    onClick(timer.userInfo as! NSEvent)
}


func onClick(theEvent: NSEvent) {
    println("single")
}


func onDoubleClick(theEvent: NSEvent) {
    println("double")
}
Baronetcy answered 21/8, 2015 at 13:32 Comment(0)
C
8

Generally applications look at clickCount == 2 on -[mouseUp:] to determine a double-click.

One refinement is to keep track of the location of the mouse click on the -[mouseDown:] and see that the delta on the mouse up location is small (5 points or less in both the x and the y).

Chud answered 21/3, 2013 at 15:5 Comment(1)
IMHO this is the most useful answer as it also answers the implicit question of whether to react on second mouseDown or second mouseUp. Of course, in certain situations it might make sense to deviate from the standard. Unfortunately, Apple themselves don't quite adhere to one standard (Finder and Mail on up, Calendar on down)…Corrupt
M
2

An alternative to the mouseDown: + NSTimer method that I prefer is NSClickGestureRecognizer.

    let doubleClickGestureRecognizer = NSClickGestureRecognizer(target: self, action: #selector(self.myCustomMethod))
    doubleClickGestureRecognizer.numberOfClicksRequired = 2

    self.myView.addGestureRecognizer(doubleClickGestureRecognizer)
Melva answered 1/10, 2018 at 8:43 Comment(2)
this presents the exact sample issue as with mouseDown: if you have two gesture recognizers for single and double click, the single click selector is called also in case of double clickPegboard
This is overkill for this problem and is not the right approach. Chuck's solution is correct: get the clickCount of the NSEvent in -mouseUp: or -mouseDown:Heshvan
U
1

I implemented something similar to @jayarjo except this is a bit more modular in that you could use it for any NSView or a subclass of it. This is a custom gesture recognizer that will recognize both click and double actions but not single clicks until the double click threshold has passed:

//
//  DoubleClickGestureRecognizer.swift
//

import Foundation
/// gesture recognizer to detect two clicks and one click without having a massive delay or having to implement all this annoying `requireFailureOf` boilerplate code
final class DoubleClickGestureRecognizer: NSClickGestureRecognizer {

    private let _action: Selector
    private let _doubleAction: Selector
    private var _clickCount: Int = 0

    override var action: Selector? {
        get {
            return nil /// prevent base class from performing any actions
        } set {
            if newValue != nil { // if they are trying to assign an actual action
                fatalError("Only use init(target:action:doubleAction) for assigning actions")
            }
        }
    }

    required init(target: AnyObject, action: Selector, doubleAction: Selector) {
        _action = action
        _doubleAction = doubleAction
        super.init(target: target, action: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(target:action:doubleAction) is only support atm")
    }

    override func mouseDown(with event: NSEvent) {
        super.mouseDown(with: event)
        _clickCount += 1
        let delayThreshold = 0.15 // fine tune this as needed
        perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)        
        if _clickCount == 2 {
            _ = target?.perform(_doubleAction)
        }
    }

    @objc private func _resetAndPerformActionIfNecessary() {
        if _clickCount == 1 {
            _ = target?.perform(_action)
        }
        _clickCount = 0
    }
}

USAGE :

let gesture = DoubleClickGestureRecognizer(target: self, action: #selector(mySingleAction), doubleAction: #selector(myDoubleAction))
button.addGestureRecognizer(gesture)

@objc func mySingleAction() {
 //  ... single click handling code here
}

@objc func myDoubleAction() {
 // ... double click handling code here
 }
Universality answered 15/4, 2018 at 15:55 Comment(0)
K
0

Personally, I check the double click into mouseUp functions:

- (void)mouseUp:(NSEvent *)theEvent
{

    if ([theEvent clickCount] == 2)
    {

        CGPoint point = [theEvent locationInWindow];
        NSLog(@"Double click on: %f, %f", point.x, point.y);

     }

}
Kolyma answered 10/11, 2015 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.