Object under mouseDown COCOA - objective-c

I have a pretty simple question for which I could not find a simple answer.
When using cocoa (osx, xcode) and a method called "mouseDown" which detects if mouse has clicked on a view, how to detect on which object mouse has clicked? I just need a class name so I can know if the user has clicked on, for example NSImageView, WebView, NSTextView or on a NSView it self? Or even better, if I have two NSImageViews on my NSView, how to detect on which one it was clicked?
Cheers.

In your view mouseDown method, you can call the hitTest: method to get the farthest descendant of the receiver in the view hierarchy that was clicked:
So in your view subclass, you could do something like:
- (void)mouseDown:(NSEvent *)theEvent
{
id clickedObject = [self hitTest:[theEvent locationInWindow]];
if ([clickedObject isKindOfClass:[NSImageView class]]) {
NSLog(#"Clicked an ImageView");
} else if ([clickedObject isKindOfClass:[WebView class]]) {
NSLog(#"Clicked a WebView");
}
}
Your question seems a bit odd though, because normally you don't need to do this hit testing yourself.
If you're trying to get a click event when a particular image is clicked, a better way would be to use a borderless button with an image set and then implementing an action method and connecting that to the button.

Related

keyDown: Not Called on NSClipView Subclass

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

Show NSWindow on right click in NSTableView

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.

NSView's context NSMenu is never shown even though all the right methods are being called

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.

Why is it that my UITableViewController setEditing:animated: method is not called?

I must be doing stupid, but I can't see what: my UITableViewController subclass is never called when the edit button of my navigation is pressed.
What could be causing that?
My view hierarchy is loaded from a Nib file and put inside a popover. The [+] button is connected to the insertNewObject action of my UITableViewController subclass. It works fine.
The [Edit] button however has no action to connect to. The doc says it will automatically call the setEditing:animated: method of the view controller, which I override.
The nib file is set up pretty much as usual AFAICT. And in fact, I'm not sure what additional detail I can give that would suggest my mistake.
What is the control flow from the click on the [Edit] button to the call of the setEditing:animated method?
I feel like we must be missing the same thing.
Whatever the case, I made it work by doing the following.
IBOutlet UIBarButtonItem *editButton;
-(IBAction)editButtonPressed:(id)sender {
[self setEditing:YES animated:YES];
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
if(self.tableView.isEditing)
{
self.editButton.style = UIBarButtonItemStylePlain;
self.editButton.title = #"Edit";
}
else
{
//self.editButton.style = UIBarButtonSystemItemDone;
self.editButton.style = UIBarButtonSystemItemEdit;
self.editButton.title = #"Done";
}
// Toggle table view state
[super setEditing:!self.tableView.isEditing animated:animate];
}
I hooked the editButton up to the button I added to the nav bar and it's action to the editButtonPressed IBAction. After doing that my setEditing: is called (obviously) and the super call toggles the table view's editing state.
I'd like to use the system defined button styles, but the appropriate one is commented out because while it did change style I couldn't figure out how to change the text from "Edit" to "Done" so I had to do it all manually (that only worked if I left the button as Custom and set the style generically). This has the downside of not being localized (for free), etc.

Best practice to pass a value from pop over control on iPad

It is an iPad app based on SDK 3.2.
I have a MainUIView, that is subclass from UIView, it have a UIButton and a UILabel. When user press the UIButton, the pop over control will be appeared with a table view. When the user select a cell from the table view, the UILabel changes content base on the user click, and the pop up table view will disappear.
The question is, how can I pass the "selected cell" to the UILabel. I am thinking making a "middle man" object. When the user click the UIButton, and the "middle man" will pass to the table. When the cell is selected, the "middle man" will store the idx, and call the UILabel change content from the value of "middle man".
But I think it is pretty complex to implement, is there any easier way to implement it? thz u.
The standard way to do this is to call a delegate method when the popover closes with a selected value. The method will have been created on the view controller that calls the popover and handles setting the value.
In your view controller:
- (void) popoverDone:(id)sender {
label.text = [sender someValue];
[sender dismissPopoverAnimated:YES];
}
And in the popover:
- (void)tableView:tableView didSelectRowAtIndexPath:indexPath {
[delegate performSelector:#selector(popoverDone:) withObject:self];
}
There are other ways of doing this, but the principle is the same.