Receive trigger when active NSView focus changes - macos

I'm surprised that I haven't been able to find an answer for this from searching. So if there is a page describing how to do this, let me know, but I've been unable to find it.
I have 3 sibling NSOutlineViews all inside an NSSplitView I have added observers for NSOutlineViewSelectionDidChange which, when triggered update an NSTextView editor. I even test for negative row values to indicate that the user has unselected the row and clear the text.
The NSOutlineViews are connected to custom datasource objects and the NSSplitView is created by my custom NSWindowController in turn created by my custom NSDocument and custom NSDocumentController (I'm not using .nibs)
However I cannot seem to receive triggers for the NSOutlineViews changing their active status, it works if the user selects a different row in a different NSOutlineView, as the selection has changed but if they click the selected row of a different view, I don't receive any event or notification that anything has changed. Visually I can see the change as the row selection highlight colour changes from coloured to grey, in the outline view that has lost focus and the selection row colour changes from grey to coloured in the newly activated view.
I've tried to catch mouseDown events, tried becoming first responder, tried observing changes in the NSSplitView I've been right through the NSObject hierarchy from NSOutlineView to NSResponder looking for the appropriate notification or method. I found deprecated documentation regarding a focus change notification. I've tried combinations of nsview, nsnotification, nsoutlineview and various actions in google but can't find the 'this is how you do it'
EDIT:
this is the code I've added to my NSOutlineView subclass (along with prototypes in the headers) to become first responder, but it is never triggered.
- (BOOL)acceptsFirstResponder { return YES; }
- (BOOL)becomeFirstResponder {
NSLog(#"becomefirstResponder %#",self);
return YES;
}

Related

Disabling the NSTableView row focus ring

How do you disable the focus ring around an NSTableView row when the user right-clicks on it? I can't get it to disappear. Setting focus ring of an individual NSTableViewCell in the table to None has no effect.
Subclass the table view and implement the following method:
- (void)drawContextMenuHighlightForRow:(NSInteger)row {
// do nothing
}
Note:
The blue outline is not the focus ring.
This is an undocumented private method Apple uses to draw the outline. Providing an empty implementation will prevent anything from being drawn, but I am not 100% sure that whether it can pass the review.
New:
Here is how I did it.
You can handle the menu manually. Subclass NSTableRowView or NSTableCellView, then use rightMouseDown: and mouseDown: (check for control key) and then notify your tableViewController (notification or delegate) of the click. Don't forget to pass the event as well, then you can display the menu with the event on the table view without the focus ring.
The above answer is easier, but it may not pass the review, as the author mentioned.
Plus you can show individual menu items for each row (if you have different sorts of views)
Old:
I think the focus ring is defined by NSTableRowView, not NSTableCellView, because it is responsible for the complete row. Try to change the focus ring there. You can subclass NSTableRowView and add it to the tableView via IB or NSTableViewDelegate's method:
- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
If your goal is just displaying a contextual menu, you also can try this.
Wrap the NSOutlineView within another NSView.
Override menuForEvent method on the wrapper view.
Do not set menu on outline-view.
Now the wrapper view shows the menu instead of your outline-view, so it won't show the focus ring. See How do you add context senstive menu to NSOutlineView (ie right click menu) for how to find a node at the menu event.

Editable NSTextField in NSTableView

I have a view based NSTableView with some labels in my customized and subclassed view. One of the label should be editable, so therefore I set this NSTextField to editable.
But now I have two problems, I can't solve:
1) If I move the mouse over the editable NSTextField, the cursor don't change to the IBeamCursor (the edit cursor).
2) I need to double click at the label, to be able to edit. I want to have a single click. I found some solutions for this problem here at stackoverflow, the best one is to override the acceptsFirstResponder to return always true, but then, clicking at the NSTextField selects the whole text instead of placing the cursor at the clicked position.
Sorry... this is a duplicate. I found this:
NSTableView - select row and respond to mouse events immediately
You have to subclass NSTableView. My swift code:
class TableViewEditing: NSTableView {
...
override func validateProposedFirstResponder(responder: NSResponder, forEvent event: NSEvent?) -> Bool {
return true
}
}
EDIT:
Just one disadvantage: Sometimes entering the edit mode, it seems that the text is just shortly selected and deselected. But you can see, that this is a cocoa problem, it's the same for example in Apple reminders app.

Maintaining focus or first responder state of NSOutlineView

I have a subclass of an NSOutline. When it loses focus, the disclosure triangle of a selected row changes from its inverted template form (white) back to black. I don't want this to happen: I want the appearance of a selected row to stay the same, as in XCode or Mail.
I've tried intercepting firstResponder-based messages in the NSOutlineView subclass, as well in my custom rowViews, but to no avail. Any ideas?
In my subclass of NSTableRowView, doing the following works:
- (BOOL)isEmphasized {
return YES;
}

Validating a drag to an NSCollectionView isn't reflected visually

I have an NSCollectionView that I want to accept items dragged from elsewhere in my application.
I implement collectionView:validateDrop:proposedIndex:dropOperation: and collectionView:acceptDrop:index:dropOperation: in the collectionview's delegate and register for the appropriate dragged types. Both methods get called fine when I drag the appropriate types, but I don't get a blue focus ring over the collectionview indicating a valid drag.
Have tried both the collection view and its containing scroll view on Default and External settings for the focus ring. Both are just the standard non-derived Cocoa classes. Wondered if there was anything else I should try. Surely it isn't necessary to subclass NSCollectionView for this?
Thanks
Chris
Focus rings are not typically the correct way to provide feedback about drag destinations. Every view does it slightly differently. NSTextView shows the insertion bar. NSTableView shows a blue line in between rows for Before drop operations, and shows a bezel around the row for On drop operations. (See NSTableViewDropOperation)
NSCollectionView shows a "gap" between existing subviews to show where the items will be dropped for Before drop operations, and it will set the selected property on NSCollectionViewItem to YES for On drop operations. (Note: NSCollectionViewItem doesn't do anything by default to visibly represent the selected property. You must implement that yourself.)
Since NSCollectionView's feedback uses existing subviews only, it appears there isn't any feedback at all for empty NSCollectionView's. You would need to subclass to provide this behavior yourself. You could also file a bug to request that NSCollectionView do this itself.

NSTextView overlay causes oddities with first responder status

I have an NSTextView in an NSScrollView, and I am programmatically inserting an NSView subclass as a subview of the NSTextView. This NSView acts as an overlay, superimposing graphical information about the text beneath it.
I thought it was working quite well until I noticed that the text view does not respond to right clicks. Other operations (editing, selection) seem to work just fine.
Also, if the first responder is changed to a sibling of the scroll view (an outline view, for example) the text view does not regain first responder status from clicking on it. The selection will change in response to clicking, but the selection highlight is gray instead of blue (indicating that the text view is not the first responder).
If I offset the frame of the overlay subview, the text view behaves 100% normally in the area not overlapped by the overlay, but the overlapped area behaves incorrectly, as outlined above.
Steps To Replicate This Behavior on Mac OS X 10.6.4:
Create a plain old non-document-based Cocoa app.
Add an `NSTextView' IBOutlet to the app delegate .h.
Add an NSTextView to the window in MainMenu.xib. Connect the textView outlet.
Type in a bit of code:
In applicationDidFinishLaunching:
NSView *overlay = [[NSView alloc] initWithFrame:textView.bounds];
[textView addSubview:overlay];
[overlay release];
Run the app, observe that right click in the text area does not work as it should, yet you can still otherwise interact with the text view.
Next, add an NSOutlineView to the window in the xib. Observe that once focus leaves the text area (if you click on the outline view) with the overlay in place, you cannot set the focus back to the text view (it will not become first responder again).
Is there some way I can enable the NSTextView to receive all of its events, even though my NSView overlay does not accept first responder or mouse events? I suspect this might be related to the field editor – perhaps it is ignoring events it thinks are destined to the overlay view?
You probably need to make your overlay an instance of a custom view class that forwards all events and accessibility messages to the text view. You may also need to convert any view-relative coordinates to the text view's coordinate system.
I don't have a lot of experience with it, but another possibility would be to use a Core Animation layer as an overlay.
A clean way to handle this is by making your overlay view a custom subclass of NSView, and then overriding the hitTest: method to always return nil. This will prevent the overlay view from participating in the responder chain. Instead, events will get sent automatically to it's superview or views higher up the view hierarchy. You might also want to override acceptsFirstResponder to return NO to be safe (in case it's accidentally set programatically).

Resources