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

后端 未结 5 1006
一向
一向 2021-02-01 21:05

I have a custom NSView (it\'s one of many and they all live inside an NSCollectionView — I don\'t think that\'s relevant, but who knows). When I click

相关标签:
5条回答
  • 2021-02-01 21:29

    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.

    0 讨论(0)
  • 2021-02-01 21:30

    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)

    0 讨论(0)
  • 2021-02-01 21:32

    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.
    }
    
    0 讨论(0)
  • 2021-02-01 21:38

    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...

    0 讨论(0)
  • 2021-02-01 21:50

    @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.)

    0 讨论(0)
提交回复
热议问题