Disabling the NSTableView row focus ring - macos

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.

Related

Receive trigger when active NSView focus changes

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;
}

How do I do something after the next NSView -layout has occurred?

In response to a user event, I want to:
add a new NSView to the window, and then
show an NSPanel positioned just below that view
I have each half of this done. I can add a new subview, and the container view's -updateConstraints identifies it and adds the correct layout constraints, so that the next time layout is performed, it's positioned correctly in the window. Also, I have a NSWindowController subclass that puts the panel on the screen.
Unfortunately, there's an ordering problem. My panel's controller just looks at the new NSView's frame property for deciding where to put it, but during this iteration of the main event loop, the -layout method hasn't been called yet, so it's still positioned at (0,0).
(If I separate these two pieces of functionality, and require two separate user events for "add view" and "create panel", then the panel is correctly positioned below the view.)
Is there a way to attach an NSPanel to an NSView, as if with a layout constraint? Or is there a way to say "do this (window controller stuff), but only after the next -layout call"?
Just call -layoutSubtreeIfNeeded on your NSView’s superview as soon as you add it and its constraints, so it will lay out immediately, then add the panel.
Or use an NSPopOver, although those draw a certain way and you might not want that.

Table cell only is highlighted when you tap on the disclosure icon

I place a single table view cell on my view using interface builder and add an accessory type, a selection color and enable user interaction.
My problem is that the cell only is highlighted when the user taps on the arrow icon. But it also should be highlighted when the user taps on the text label inside it, he can tap anywhere on the cell.
How can I achieve this?
A UITableViewCell is really only meant to be used within the context of a UITableView (which knows how to handle and/or delegate the selection & highlighting of the cell).
Having a UITableViewCell outside of the context of a table is a somewhat unexpected user interface.
Couldn't you do what you want to do with a UIButton instead?
Or a UIView that has two UIButton subviews (or at least one UIButton subview to contain a widget graphic that looks like the disclosure accessory)? That way the things you want to get highlighted would definitely happen, as opposed to depending on a nonexistent [UITableView didSelectCellAtIndexPath:] method that doesn't exist in a view that isn't a UITableView delegate.

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.

NSTableView jumps on selection… why?

I've got an NSTableView with a single column, populated with NSTableViewDataSource methods (no bindings involved.) The NSTableView is inside an NSScrollView, as is the default behavior when you drag an NSTableView in from the library in Interface Builder. The contents of the tableview are populated based upon a search string that the user types, and are not changed after that point.
I am only implementing these two methods of the DataSource protocol:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
After typing my search string, the focus is still in the NSTextField, but I can naturally scroll the tableview with my mouse's scroll wheel or with the arrows on the scroll bar. Neither causes the tableview to receive the focus ring. However, if I scroll down such that I am not at the top of the tableview and then click to highlight an individual row in the tableview, the tableview instead jumps back to the top, and then selects whatever row is under my mouse at that point. If I click on rows after that point, it works as expected.
In other words, if the NSTableView has the focus ring around it, clicking on a row highlights the expected row. If it does not have the focus ring around it, clicking on a row selects whatever row is at that position after scrolling to the top.
Any insight? I am running Snow Leopard, but I believe it happened when I was running Leopard as well.
Could it be that when the search NSTextField loses focus, it somehow fires a reloadData (or similar method) on the table view, which would empty out the view (resetting its scrolling position to the top) and then re-populating it instantaneously?
I had a similar bug once, and the cause was that my window controller was handling awakeFromNib and was setting the scroll position on the list so it would be in the right position when first loaded. However, I eventually realised that it was also getting awakeFromNib messages from the nib-based table view cells which are created as needed, which would sometimes mess with the table scroll value unexpectedly while scrolling. My fix was to make the window controller only set scroll position when handling the first awakeFromNib.

Resources