Tabbing between NSTextFields with nextKeyView - macos

I have a single NSViewController with the following layout, set using a storyboard:
The nextKeyView outlet of each of the NSTextFields is configured to be the next NSTextField in the order presented on the screenshot. For example, I chose the server NSTextField in IB and dragged from the nextKeyView outlet in Connectivity inspector to the login NSTextField, and did the same for the rest of the fields.
When the app is launched, any tab press on any of the field moves the selection to the first NSTextField. How do I achieve the desired tabbing between the fields?
I tried this in the respective WindowController, but to no avail:
- (void)windowDidLoad {
[super windowDidLoad];
self.window.initialFirstResponder = self.serverTextField;
}

This seems to be the most detailed answer, from Justin Bur posted to cocoa-dev mailing list (31 Jan 2007).
On several occasions over the years, people have asked why their key
view loop doesn't work properly. Most of these queries never get
answered on the list. After failing to find help for my key view loop
problems either on this list or on web sites, I did some
experimenting.
The key view loop can be problematic to deal with. It is designed to
just work magically, so in most cases it's not an issue. But if it
doesn't work, it's pretty difficult to figure out why not. Here are
some guidelines for getting a working key view loop.
Consider whether you can settle for an automatically generated key view loop. Each responder's top left corner determines its placement
in the loop. The loop proceeds from upper left to lower right, row by
row (at least for left-to-right scripts). This is by far the easiest
solution. To enable this, make sure the window's initialFirstResponder
is nil. See also -[NSWindow recalculateKeyViewLoop].
If the automatic key view loop is not suitable, set up your own key view loop using Interface Builder as much as possible. The window's
initialFirstResponder outlet must be set, in order to disable
automatic key loop generation. From that responder around the loop,
set the nextKeyView outlet of each item in the loop. (If desired, the
last item's nextKeyView can be set to the first item, thus closing the
loop.) For any view with scrollbars (NSTextView, NSTableView, etc.),
you should use the enclosing NSScrollView when setting nextKeyView.
If you have any responders created in code, splice them into the key view loop early (preferably in awakeFromNib
or maybe -[NSWindowController windowDidLoad]).
For each (sequence of) new item(s), you must use call -[NSView setNextKeyView:] thus: once to make
the previous item point to the (first) new one, (calls to make each
new item point to the next), and finally to make the (last) new item
point to its successor.
If your window has a toolbar, toolbar items that are interested in becoming key view will automatically add and remove themselves as the
toolbar is shown or hidden. The toolbar does not take into account the
return value of -[NSWindow autorecalculatesKeyViewLoop]. Toolbar items
are always placed in the loop before the top leftmost item. There is
no easy way to change this.
Once the window has been displayed, it can be extremely difficult to modify the key view loop - in particular if you are using
NSScrollView or NSTabView. These (and others?) are special cases
because they insert their contained views into the loop automatically.
For information on the initialFirstResponder and key view loop of an
NSTabViewItem, see the AppKit release notes for OS X 10.1
.
If you have items that should sometimes be in the loop and other times not, it is not advisable to attempt to splice them in and out of
the loop. Instead, subclass -[NSResponder acceptsFirstResponder] for
these items. If an item returns NO from this method, it will be left
out of the loop (temporarily); if it returns YES, it will come back
into the loop. Alternately, if the item is derived from NSControl (it
probably is), you can call setRefusesFirstResponder: on it.
If you make a mistake, your key view loop will cease to function, either in one direction or in both. Once it breaks it stays broken. To
debug, comment out calls to setNextKeyView: or
setInitialFirstResponder: until it works again. The offending call is
likely trying to modify the key view loop in the presence of
NSScrollView or NSTabView, after these objects have already done their
behind-the-scenes loop-munging. Move the calls to an earlier point, or
do without. (If you have no calls to setNextKeyView:, then check your
nib - make sure the window's initialFirstResponder is set and that
nextKeyView outlets are chained together the way you want.)
In System Preferences/Keyboard & Mouse/Keyboard Shortcuts, at the bottom of the pane under "Full keyboard access", you can control
whether key view loops include all controls or only text fields and
scrolling lists (^F7 to toggle). You should test your key view loops
with this setting in each state.
These guidelines were determined by experiment and may not be entirely
accurate. Corrections and further explanations are most welcome.

Set the window's initialFirstResponder in windowDidLoad of the window controller or viewWillAppear of the view controller. If initialFirstResponder isn't set before the window's makeKeyAndOrderFront, recalculateKeyViewLoop is called.

Related

Getting NSTableCellView inside tableView(_:heightOfRow:)

I understand that you cannot call view(atColumn:row:makeIfNecssary:) from within tableView(_:heightRow:).
Ideally I'd like to derive height based on a button state within the NSTableCellView. I can think of a couple ways to work around this.
Use objectValue in my custom NSTableCellView to keep track of the state of the button.
Only enable buttons on selected row items and maintain a list of the selected NSTableCellView.
Are these recommended or is there another recommended way to handle this.
Also, is it recommended for a NSTableCellView to know what row/col it belongs too? Or is this something that also should be maintained in objectValue?
My use case is will have a disclosure arrow to expand the cell. I've tested it out and it works, however at this point, I'm using a test flag variable to control the height values not the actual button state.

Understanding first responder behavior

From the app delegate, I make a window and set it's contentView to be a view programmatically generated from a plist specification. I then bring the window to front. The window has a toolbar, and when the buttons on the toolbar are pressed, it is supposed to display a different contentView.
I have found that the first content view appears with its topmost text field subview already selected as first responder, but changing the view from the toolbar (it sets contentView on the window) to a different view will not select any of that view's text fields as first responder.
I want to have consistency, so ideally either it would never auto-select a control as first responder or it would always auto-select a control as first responder, but I don't really understand what process is making the control first responder in the first place.
Could somebody please explain what is causing that, so I can either prevent it or try to emulate it when switching views?
hussain Shabbir's answer is on the right track, but misses a few things.
First, setting the window's initial first responder, and then making the same view its first responder, is redundant. The point of the first is to cause the second.
Second, you need to set the window's initial first responder before making the window visible:
Sets a given view as the one that’s made first responder (also called the key view) the first time the window is placed onscreen.
If the window is already visible when you set its initial first responder, nothing will happen.
You need to set the initial first responder before you make the window visible for the first time.
The best place to do that is not in code at all—it's in the nib.
You would then not have either of those lines of code.
Better yet:
The window has a toolbar, and when the buttons on the toolbar are pressed, it is supposed to display a different contentView.
Have you considered using NSTabView? It handles this automatically (every tab view item has its own initial first responder outlet).
If you want when you click on different views your textfield should be become first responder, Then the two lines of code below should work:-
Here on the basis of your condition use these below lines:-
[[self window] setInitialFirstResponder:(NSView *)YourTextFieldName];
[[self window] makeFirstResponder:(NSView *)YourTextFieldName];

Setting a custom tab order in a Cocoa application

I have a window with two columns of fields. On the left, there is an NSTableView and an NSTokenField, and on the right, there are two NSTextFields. I want the tab order to go down the left, then down the right. (So the order should be NSTableView, NSTokenField, NSTextField, NSTextField in my window.) However, Cocoa appears to be determining its own preferred order, going from the top to the bottom. The NSTokenField is positioned lower in the window than any other control, so it will always tab from NSTableView, to the right NSTextFields, then back to the bottom left NSTokenField.
I have tried following this section of the Apple developer documentation called Enable Tabbing Between Text Fields and dragging nextKeyView in Interface Builder between the fields in the order that I want. This seems to have absolutely zero effect on the tab order, and from what I can tell, Cocoa appears to still use its default detecting method to choose a tab order.
Any ideas? My target is 10.6+.
Make sure that you also set the initialFirstResponder outlet of the window to the first field (the table view in this case).
Sounds like you're going to have to do it programmatically:
Register for controlTextDidEndEditing notifications, identify the field by tag, and then call makeFirstResponder:fieldOfYourChoice on the window. And/or use an IBAction on the field, identifying it by sender, and call makeFirstResponder.

What is a NSBrowserTableView as compared to an NSBrowser?

I'm implementing a -(void)delete: method so I can handle the delete key in my Cocoa app. I want it to do different things depending on what's selected: for text-fields, I want the default behaviour (remove char to the left), but for NSBrowser items, I want it to delete the item.
I thought I would ask the Window for it's first responder, and then see if that first responder is equal to the pointer for my NSBrowser, but it never matched. When I debug it, I find that the firstResponder is pointing to an instance of NSBrowserTableView, but I can't find that in the documentation.
What is it?
And how else could I test to see if my firstResponder is a particular tableView? (I Thought of subclassing NSBrowser but I tend to avoid subclassing, and my second thought was to add a tag, but I like my first method best, if only the firstResponder would point to my NSBrowser instance when one of the items in the browser is selected. )
Thoughts?
Actually, #trudyscousin is only partially correct. This class is definitely not a subclass of NSBrowser.
NSBrowserTableView is a private subclass of NSTableView used by NSBrowser to display each column. The table view is used so there is a separate place to draw the branch image (the little arrow drawn next to folders) while leaving the rest of the row to be drawn by either the default or user-defined cell.
If you think about it, it actually makes sense that the table view (rather than the browser) be the first responder, because then the table for the active column gets first crack at responding to keystrokes, and NSBrowser can let NSTableView do what it already knows how to. (For example, jumping to the first row that matches a letter typed by the user.)
Fortunately, NSBrowserTableView has a pointer back to the browser it works for. You can access this via its -(NSBrowser*)browser method. I recommend you don't subclass NSBrowser for this particular case, since you'd have to have a deep knowledge of its private implementation to do anything useful.
You can't find that in the documentation because it's private. My guess is that, when you instantiate a NSBrowser or a NSTableView, you're actually instantiating a subclass of this private class, which itself is a subclass of NSControl (which is pointed out in the documentation as being the superclass of both NSBrowser and NSTableView). Another example is NSString represented as 'NSCFString,' which I take as an allusion to the fact that CFString and NSString are "toll-free bridged."
Take this with as many grains of salt as you wish, but the way I'd go about gaining insight into the first responder is inserting a NSLog statement in my code and breaking just beyond it, seeing what was printed in the log. You could set the view's tag and display that in the statement. Or you could ask for your first repsponder's class
NSStringFromClass([myFirstResponder class])
and display that.
Hope this helped.

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.

Resources