Distinguishing a single click from a double click in Cocoa on the Mac

浪尽此生 提交于 2019-12-02 21:49:51

Delaying the changing of the selection state is (from what I've seen) the recommended way of doing this.

It's pretty simple to implement:

- (void)mouseUp:(NSEvent *)theEvent
{
    if([theEvent clickCount] == 1) {
        [model performSelector:@selector(toggleSelectedState) afterDelay:[NSEvent doubleClickInterval]];
    }
    else if([theEvent clickCount] == 2)
    {
        if([model hasBeenDownloaded])
        {
                [NSRunLoop cancelPreviousPerformRequestsWithTarget: model]; 
                [mainWindowController showPreviewWindowForPicture:model];
        }
    }
}

(Notice that in 10.6, the double click interval is accessible as a class method on NSEvent)

If your single-click and double-click operations are really separate and unrelated, you need to use a timer on the first click and wait to see if a double-click is going to happen. That is true on any platform.

But that introduces an awkward delay in your single-click operation that users typically don't like. So you don't see that approach used very often.

A better approach is to have your single-click and double-click operations be related and complementary. For example, if you single-click an icon in Finder it is selected (immediately), and if you double-click an icon it is selected and opened (immediately). That is the behavior you should aim for.

In other words, the consequences of a single-click should be related to your double-click command. That way, you can deal with the effects of the single-click in your double-click handler without having to resort to using a timer.

Personally, I think you need to ask yourself why you want this non-standard behaviour.

Can you point to any other application which treats the first click in a double-click as being different from a single-click? I can't think of any...

Add two properties to your custom view.

// CustomView.h
@interface CustomView : NSView {
  @protected
    id      m_target;
    SEL     m_doubleAction;
}
@property (readwrite) id target;
@property (readwrite) SEL doubleAction;

@end

Overwrite the mouseUp: method in your custom view.

// CustomView.m
#pragma mark - MouseEvents

- (void)mouseUp:(NSEvent*)event {
    if (event.clickCount == 2) {
        if (m_target && m_doubleAction && [m_target respondsToSelector:m_doubleAction]) {
            [m_target performSelector:m_doubleAction];
        }
    }
}

Register your controller as the target with an doubleAction.

// CustomController.m
- (id)init {
    self = [super init];
    if (self) {
        // Register self for double click events.
        [(CustomView*)m_myView setTarget:self];
        [(CustomView*)m_myView setDoubleAction:@selector(doubleClicked:)];
    }
    return self;
}

Implement what should be done when a double click happens.

// CustomController.m
- (void)doubleClicked:(id)sender {
  // DO SOMETHING.
}

@Dave DeLong's solution in Swift 4.2 (Xcode 10, macOS 10.13), amended for use with event.location(in: view)

var singleClickPoint: CGPoint?

override func mouseDown(with event: NSEvent) {
singleClickPoint = event.location(in: self)
perform(#selector(GameScene.singleClickAction), with: nil, afterDelay: NSEvent.doubleClickInterval)
 if event.clickCount == 2 {
    RunLoop.cancelPreviousPerformRequests(withTarget: self)
    singleClickPoint = nil
//do whatever you want on double-click
}
}

@objc func singleClickAction(){
guard let singleClickPoint = singleClickPoint else {return}
//do whatever you want on single-click
}

The reason I'm not using singleClickAction(at point: CGPoint) and calling it with: event.location(in: self) is that any point I pass in - including CGPoint.zero - ends up arriving in the singleClick Action as (0.0, 9.223372036854776e+18). I will be filing a radar for that, but for now, bypassing perform is the way to go. (Other objects seem to work just fine, but CGPoints do not.)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!