4

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;
}
Dmitry Serov
  • 861
  • 11
  • 22
  • Have you set the windows initialFirstResponder? – catlan May 12 '17 at 22:24
  • @catlan updated the question – Dmitry Serov May 13 '17 at 04:43
  • 1
    Make sure that nothing is nil in your NSWindowController subclasses -windowDidLoad. ie check the window, the self.contentViewController and textfield: `ViewController *vc = (ViewController *)self.contentViewController; self.window.initialFirstResponder = vc.field1;` – catlan May 13 '17 at 05:35

2 Answers2

9

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.

  1. 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].

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

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

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

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

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

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

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

catlan
  • 25,100
  • 8
  • 67
  • 78
  • This is a thorough answer, but it does not help in my situation. I have a ViewController-based responder chain; changing anything in WindowController does not seem to help. – Dmitry Serov Jun 09 '17 at 06:02
  • 2
    It's worth mentioning the [`autorecalculatesKeyViewLoop`](https://developer.apple.com/documentation/appkit/nswindow/1419214-autorecalculateskeyviewloop?language=objc) property of `NSWindow`, which is what I ended up using. – aleclarson Feb 25 '19 at 01:39
  • @aleclarson You should turn your comment into an answer. If you do, I will vote it up, because that also solved my problems. – jvarela Jan 06 '20 at 00:10
0

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.

Willeke
  • 14,578
  • 4
  • 19
  • 47