All,
So I have a subclass of NSBox and have subviews in it like a label and two imageViews. I have overridden menuForEvent: in it. However, when I click on the NSBox to select it and then later Control+Click on any of its subviews then menuForEvent: is never called.
I don't understand why that is the case.
There is a difference in how control-clicks and right-clicks are handled by NSView (as jfewtr pointed out). Contextual menus will appear for a right-click if the click falls within a subview, but not for a control-click.
I was surprised by this and actually wrote a post about it with more details here: NSView control-click quirks
There are a couple potential solutions, but overriding/customizing your entire subview tree is probably not the best choice. I've found the best fix for this is to display your contextual menu explicitly in your top-level view (your NSBox subclass) for a control-click:
- (void)mouseDown:(NSEvent *)theEvent
{
if (theEvent.modifierFlags & NSControlKeyMask)
{
[NSMenu popUpContextMenu:[self menuForEvent:theEvent] withEvent:theEvent forView:self];
}
}
While it's not great to hardcode this behavior, it avoids manipulating or traversing your entire subview tree, which can incur more problematic side effects/bugs.
You need to implement menuForEvent: in the subviews too, and forward the event to your superview's (NSBox subclass) implementation of menuForEvent:
- (NSMenu *)menuForEvent:(NSEvent *)event
{
return [[self superview] menuForEvent:event];
}
I assumed that it would automatically fall through to the superview without the need for subclassing the subviews. I found that a right-click does, but, for some reason, a control-click does not.
Related
My app is not document based, and its sole window is managed by a custom, xib-based NSWindowController subclass that I instantiate within the app delegate code:
- (void) applicationDidFinishLaunching:(NSNotification*) aNotification
{
_mainWindowController = [MainWindowController new];
// (stored in ivar just to prevent deallocation)
//[_mainWindowController showWindow:self];
// ↕︎ Not sure about the difference between these two... both seem to work.
[[_mainWindowController window] makeKeyAndOrderFront:self];
}
I have subclassed NSClipView to "center content inside a scroll view" (instead of having it pegged to the lower left corner) when it is zoomed to a size smaller than the clip view, and also implement custom functionality on mouse drag etc.
My window does have a title bar.
My window isn't borderless (I think), so I am not subclassing NSWindow.
I have overriden -acceptsFirstResponder, -canBecomeKeyView and -becomeFirstResponder in my NSClipview subclass (all return YES).
The drag events do trigger -mouseDown: etc., and if I set a breakpoint there, the first responder at that point is the same as the window hosting my clip view: [self.window firstResponder] and [self window] give the same memory address.
What am I missing?
Update
I put together a minimal project reproducing my setup.
I discovered that if my custom view is the window's main view, -keyDown: is called without problems. But if I place a scroll view and replace its clip view by my custom view (to do that, I need to change the base class from NSView to NSClipView, of course!), -keyDown: is no longer triggered.
I assume it has something to do with how NSScrollView manages events (however, as I said before, -mouseDown:, -mouseDragged: etc. seem to be unaffected).
I also discovered that I can override -keyDown: in my window controller, and that seems to work, so I have decided to do just that (still open to an answer, though). Also, since I'm trying to detect the shift key alone (not as a modifier of another key), I'd rather use:
- (void) flagsChanged:(NSEvent *) event
{
if ([event modifierFlags] & NSShiftKeyMask) {
// Shift key is DOWN
}
else{
// Shift key is UP
}
}
...instead of -keyDown: / -keyUp: (taken from this answer).
I'd like to display an NSWindow when right clicking an item in an NSTableView, similarly to how the available outlets are shown in Interface Builder when you right click an object:
Unfortunately you can only use an NSMenu subclass as the menu property.
I also didn't find a delegate method of NSTableView that notifies about right clicks.
I was able to subclass NSTableView and implement rightMouseDown: and rightMouseUp: to be notified about those events, but if I set the menu property of the row cells to nil, they are not highlighted when right clicked, even though I call the super implementation):
- (void)rightMouseDown:(NSEvent *)theEvent {
[super rightMouseDown:theEvent];
NSPoint eventLocation = [theEvent locationInWindow];
eventLocation = [self convertPoint:eventLocation fromView:nil];
NSInteger rowIndex = [self rowAtPoint:eventLocation];
NSLog(#"Right clicked at row index %d", rowIndex);
}
I would like to have the highlight effect in the image below but display a window instead of the context menu:
First for the right click: explicitly select the row on right click (e.g. via this message). Then create your own NSWindow descendant, set an own NSView class as contentView and in the view you can draw the black background, rounded borders and what not. Show this window in your right click handler.
You can use an NSPopover, which works quite nicely. A popover creates a window for you, even if it is somewhat hidden. You'll get it from your controls if you send them the window message, and can register to listen for events, for instance.
The whole popover can be created in IB, and just have to implement the showRelativeToRect:ofView:preferredEdge: method in code.
To catch the right click event, you can use rightMouseDown:, which is originally defined in NSResponder, but is overridden in NSView to simply catch the event and show menu and it doesn't pass the event upwards in the responder chain (or the inheritance chain, for that matter). Hence, you simply implement that method to call showRelativeToRect:ofView:preferredEdge:.
You will typically need to have the contents in an NSViewController and its own accompanying nib file.
The NSPopover's contentViewController property can be set in IB, too.
All in all, not much code needed.
This tutorial is useful.
I know that this question must have been answered plenty of times already, but I have no idea why the touchesBegan: is not called in my UIView.
Before I added it in my app, I made a trial project to do some tests. What I did was subclass a UIView where the touchesBegan: is implemented and add this as a subview of another view. The superview has a button which shows the subclassed UIView once clicked. Everything works as expected.
However, when I did the same thing in my existing app, the touchesBegan: method, as well as the touchesMoved:, were never called. Can anyone explain what could be preventing the methods from being called?
I just found the answer to this. I realised that the superview of the UIView has the userInteraction set to disabled (which is the default value). So when I enabled the userInteraction of the superview, the touches are now recognised.
In my case background = UIColor.clearColor() was the reason why touchesBegan was not called. Its obviously not called on transparent elements.
I was struggling on this problem for a while... and i figure it out. I think that the touchesBegan override function listen on the main view: the first one on the storyboard tree of the ViewController! I would love to post an image of my storyboard to be more clear, but i can't! ;)
Anyway, IF you have subviews, they may covers the main view... in this manner the touch wont "reach" the main view.
To avoid this you'll have to set the Background property in the Attribute Inspector to Default for ALL THE SUBVIEWS. In this way the background will be transparent and the touch will be able to reach the main view.
If you don't want to set the background to transparent there is something you can do:
-Add an outlet of your last view (with background set)
-Add a tap gesture recognizer to it
-Handle the tap disposing the keyboard
ES.
In properties:
//The last view's outlet
#IBOutlet weak var ContainerView: UIView!
In viewDidLoad():
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
self.ContainerView.addGestureRecognizer(tap)
Outside:
func handleTap(recognizer: UITapGestureRecognizer){
self.view.endEditing(true)
}
Is there a proper way to determine if a NSView is actually drawn in the current view hierarchy or not, considering cases like:
The view is completely offscreen (not mandatory)
The view is not on top of the view hierarchy
The -isHidden and -isHiddenOrHasHiddenAncestor are unfortunately not set when e.g. a view disappears because a tab view switches to another tab.
The reason for this is that I have an attached child window and I would like to be able to hide it as well when the view that it is attached to is not drawn.
I have found a trick to tell if it is visible, but it requires subclassing. It works by toggling an ivar on 2 events.
- (void)discardCursorRects {
isDrawn_ = NO;
[super discardCursorRects];
}
- (void)resetCursorRects {
isDrawn_ = YES;
[super resetCursorRects];
}
Whether (or when) it's drawn is supposed to be "none of your business" and really have nothing to do with whether or not it's on-screen. Use NSView's -viewDidMoveToSuperview or -viewDidMoveToWindow to manage this.
I have an NSCollectionView with a bunch of NSViews in it, stacked vertically, to make it look a bit like UIKit's UITableView. Everything works as expected, except for one thing:
When right-clicking any one of the NSViews, I expect the NSMenu I set to be view's menu to be shown, but alas - nothing happens.
The crazy part is all the right methods are being called, exactly as could be expected: -rightMouseDown:, -menuForEvent: and finally -menu.
When I set up any object as the NSMenu's delegate, menuWillOpen: is not called, so it seems to me something fails over on Apple's side of things, just in between asking for the menu, and actually showing it.
Would anyone be able to shed a light on this?
Thanks in advance.
PS. For what it's worth, NSMenus I present manually (without relying on Apple's right-click handling) using popUpMenuPositioningItem:atLocation:inView: are shown.
Edit / Update / Clarification
The NSCollectionView in question is inside an NSWindow that's being shown when an NSStatusItem is clicked, like CoverSutra/TicToc/what have you. Some code from the MyWindow NSWindow subclass:
- (void)awakeFromNib {
[self setStyleMask:NSBorderlessWindowMask];
[self setExcludedFromWindowsMenu:YES];
}
- (BOOL)canBecomeMainWindow {
return YES;
}
- (BOOL)canBecomeKeyWindow {
return YES;
}
- (BOOL)isMovable {
return NO;
}
- (void)presentFromPoint:(NSPoint)point {
point.y -= self.frame.size.height;
point.x -= self.frame.size.width / 2;
[self setFrameOrigin:point];
[self makeMainWindow];
[self makeKeyAndOrderFront:self];
}
presentFromPoint: is the method I use to present it from any point I like, in my case from just below the NSStatusItem. (Not really relevant to this problem)
My application has LSUIElement in its Info.plist set to YES by the way, so it doesn't show a menu bar or a Dock icon. It lives in the status bar, and has a window that's shown when the NSStatusItem is clicked.
The view hierarchy is as follows:
MyWindow => contentView => NSScrollView => NSCollectionView
The NSCollectionView has an NSCollectionViewItem subclass connected to its itemPrototype property, and the NSCollectionViewItem subclass has an NSView subclass connected to its view property.
The NSView subclass, in turn, has an NSMenu connected to its menu property.
And last but not least: This NSMenu has one NSMenuItem sitting inside it.
Both the NSCollectionViewItem subclass and the NSView subclass do nothing interesting as of now, they're just empty subclasses.
The NSMenu connected to the NSView's menu property is what should be shown when the NSView is right-clicked, but as I hope I have made clear: It isn't actually shown.
Update
I still have no idea what caused this problem, but I've decided to 'move on' from NSCollectionView, as it wasn't really fit for what I was trying to do anyway, and I am now using TDListView which works like a charm.