user187676
user187676

Reputation:

Click through NSView

I have an NSView containing multiple subviews. One of those subviews is transparent and layered on top.

I need to be able to click through this view down to the subviews below (so that the view below gets first responder status), but all the mouse events get stuck on the top view (alpha is 1, because I draw stuff in it - so it should only click through transparent areas).

I actually expected this to work, since normally it does. What's wrong?

Upvotes: 11

Views: 9856

Answers (5)

Ky -
Ky -

Reputation: 32083

Here's a Swift 5 version of Erik Aigner's answer:

public override func mouseDown(with event: NSEvent) {
    // Translate the event location to view coordinates
    let convertedLocation = self.convertFromBacking(event.locationInWindow)

    if let viewBelow = self
        .superview?
        .subviews // Find next view below self
        .lazy
        .compactMap({ $0.hitTest(convertedLocation) })
        .first
    {
        self.window?.makeFirstResponder(viewBelow)
    }

    super.mouseDown(with: event)
}

Upvotes: 0

Ky -
Ky -

Reputation: 32083

Here's a Swift 5 version of figelwump's answer:

public override func hitTest(_ point: NSPoint) -> NSView? {
    // pass-through events that don't hit one of the visible subviews
    return subviews.first { subview in
        !subview.isHidden && nil != subview.hitTest(point)
    }
}

Upvotes: 0

figelwump
figelwump

Reputation: 827

Here's another approach. It doesn't require creating a new window object and is simpler (and probably a bit more efficient) than the findNextSiblingBelowEventLocation: method above.

- (NSView *)hitTest:(NSPoint)aPoint
{
    // pass-through events that don't hit one of the visible subviews
    for (NSView *subView in [self subviews]) {
        if (![subView isHidden] && [subView hitTest:aPoint])
            return subView;
    }

    return nil;
}

Upvotes: 17

user187676
user187676

Reputation:

I circumvented the issue with this code snippet.

- (NSView *)findNextSiblingBelowEventLocation:(NSEvent *)theEvent {
  // Translate the event location to view coordinates
  NSPoint location = [theEvent locationInWindow];
  NSPoint convertedLocation = [self convertPointFromBase:location];

  // Find next view below self
  NSArray *siblings = [[self superview] subviews];
  NSView *viewBelow = nil;
  for (NSView *view in siblings) {
    if (view != self) {
      NSView *hitView = [view hitTest:convertedLocation];
      if (hitView != nil) {
        viewBelow = hitView;
      }
    }
  }
  return viewBelow;
}

- (void)mouseDown:(NSEvent *)theEvent {
  NSView *viewBelow = [self findNextSiblingBelowEventLocation:theEvent];
  if (viewBelow) {
    [[self window] makeFirstResponder:viewBelow];
  }
  [super mouseDown:theEvent];
}

Upvotes: 1

NSResponder
NSResponder

Reputation: 16861

Put your transparent view in a child window of its own.

Upvotes: -1

Related Questions