NSTableView jumps on selection… why? - cocoa

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.

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

Extra Control inserted into Key View Loop in NSScrollView - Text View in Catalina

The default behavior for an NSScrollView - Text View in Catalina has changed. If the Text View has enough text to show a scroll bar, there is an extra control inserted into the Key View Loop after the NSScrollView has become firstResponder. Mojave does not show this extra control in the Key View Loop.
The 2nd control is a NSScroller contained in the Scroll View. NSScroller as firstResponder doesn't appear to have any functionality (e.g. arrow keys do not have any effect).
Can this behavior be turned off? Is there a way to make the ScrollView have a single responder in Catalina?
One thing I tried is setting "Refuses First Responder" of the NSScroller to false, but this had no effect.
Note: Full Keyboard Access (the Catalina equivalent) must be turned on to reproduce this.
I think the reason NSScroller accepts first responder is so that if all the subviews of your scroll view refuse first responder you still have a way to scroll the scroll view. While the NSScroller is first responder, if I hit page up/page down on my keyboard it scrolls the scroll view. If you want to disable this behavior you can:
scrollView.verticalScroller.refusesFirstResponder = YES;
scrollView.horizontalScroller.refusesFirstResponder = YES;

NSTableView in NSScrollView doesn't autoscroll when dragging

I'm currently implementing drag and drop rearranging in a table view in my OS X app. While normal scrolling works fine, autoscroll while dragging it totally broken.
If I grab a cell and start dragging, autoscroll just tells the table to scroll to the top. If I manually scroll using the trackpad during dragging the table continually pops to the top. If I drag one of the top cells, the table will not autoscroll down when dragging near the bottom.
I subclassed NSScrollView and overrode the scrollClipView method. I see that it's being called by some internal autoscroll method with the coordinates of (0, 0).
Since I can't see what that internal method is doing, and Goggle and SO are turning up nothing, I'm a bit stuck.
Has anyone run into this issue before? From past experiences, I have the feeling it's something AutoLayout related, but I have no idea what. Or maybe it's something completely unrelated.
Any ideas on how to further troubleshoot?
I ran into the same issue. In my case, the problem was that I set the height of the NSTableCellView to 10,000 in Interface Builder so that the horizontal separators wouldn’t be displayed for empty rows below the actual rows.
However, the actual height of my NSTableCellViews loaded at run time was 43px.
So as soon as I started dragging a cell to re-order it, the NSScrollView was trying to scroll 10,000 pixels at a time instead of 43 at a time.
This seems like a bug, because my NSOutlineView subclass does implement the following method to dynamically set the height of each row.
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat
Apparently that method is ignored by the autoscroll mechanism during drag and drop operations, and only the value set in Interface Builder is used.
So I just changed the height of the dummy NSTableCellView to 43px in Interface Builder, and I’ll just live with the horizontal separators being displayed for empty rows.

How to call NSScrollView autoscroll-method programmatically

I have simple chat application with text messages view-based NSTableView as you can see at the picture below.
Each message contains NSTextView instance having height to fit all the text.
All I need is to start NSScrollView (which NSTableView-instance is enclosed by) autoscrolling while the user selecting text dragging mouse far enough. Unfortunately, autoscrolling doesn't appear. In case of dragging somewhere outside of the text views all succeed.
I tried to call autoscroll:-method directly by simply push NSEvent-instance from NSTextView-subclass "mouse dragged"-event (like in example from this article):
- (void)mouseDragged:(NSEvent *)event
{
[self.scrollView autoscroll:event];
}
As I've overrode all the mouse events and implemented all the text selecting, this method often invokes. But the autoscrolling doesn't seem to work.
UPDATE
I figured out that before calling -autoscroll:-method there must be -mouseDown: of the same object. But it breaks my text selecting mechanism. The point even not in being first responder, there must be nothing but the mouseDown:-method.
Normally, a text view is within a scroll view of its own. Even if that's big enough to show all of the text without scrolling, it's still there. A call of -autoscroll: on anything within that scroll view (possibly including that scroll view itself?) will just try to scroll that scroll view, not the scroll view that contains the table view.
Try calling -autoscroll: on a view higher up in the hierarchy. Either self.scrollView.superview, the table cell view, or the table view.
Note, though, that the table view's scroll view will keep scrolling even after the cell view containing the text view is fully on-screen. In fact, it may keep scrolling it so far that it's off the screen in the other direction. Basically, it doesn't know that you're trying to select within the text view so it doesn't know to stop when the selection extends all the way to the edge of the text view.
Another approach might be to try to use a "bare" text view with no enclosing scroll view. I don't think IB will let you do that, so you'd have to do it programmatically. Bare text views don't play well with auto layout, though.

NSTableView displaying incorrectly in NSSplitView

I have an NStableView embedded in an NSSplitview.
The table will display, but when it does, the first three or so rows are not visible until I reize the window and/or split view. Then, it will snap into place and function perfectly fine until I quit.
Has this ever happened to anyone? Is there a simple method I can call on the view or table to get it to redraw?
This is how it displays when the view is first loaded (note: the user can scroll the table up and see the top row highlighted, but never get to it)
after resizing the window, the table view suddenly snaps into place and appears as it should:
You could try a [_yourSplitView display] to force a redraw of the NSSplitView. If I remeber correctly the SplitView will redraw all its subviews.
Try experimenting with where you use this, as result may vary depending on where in the init order you call this.
I actually got this working by calling the subview and then just resetting the position of the splitview divider.
NSView *v = [vc view];
[self.superDisplayView addSubview:v];
[self.SourceListSplitView setPosition:250 ofDividerAtIndex:0];

Resources