The Strange Case of initialFirstResponder

MacOS 10.12.5; Xcode 9b.

I’m filing this under the ‘bug reports’ tag though I think it falls under ‘expected behaviour’, but this is a bit of a knotty problem.

I had an app with a notecard, which has a title (NSTextField) and a body (NSTextView). I wanted the title to be first responder. Sadly, my body had a cursor blinking happily, even when I moved the title field to the top and left of it, and instantiated it first in IB. (In my app, this viewController is nested several layers deep in split views, but it doesn’t work properly in an app with a single viewController, either.)

Attempt #1:
body.resignFirstResponder() respectively title.becomeFirstResponder(). The documentation says ‘never use these directly’, but at this point, I wanted a proof of concept. Spoiler: Whether in viewDidLoad or viewWillAppear, this had no effect whatsoever.

Attempt #2:
NSApplication.shared.mainWindow?.makeFirstResponder(title) respectively NSApplication.shared.mainWindow?.initialFirstResponder = title

In viewDidLoad, the window is not yet set, but in viewWillAppear I could set the first responder. Spoiler: this has no effect on the application, though print(NSApplication.shared.mainWindow?.firstResponder) produces a curious effect: with initialFirstResponder, the window’s firstResponder is, nominally, the NSWindow; with makeFirstResponder, it’s the control you wanted to be first responder. Alas, the cursor still blinked happily inside the body.

Interlude: Hack
I created
class NonresponsiveTextView: NSTextView {

var isResponder: Bool = false

override var acceptsFirstResponder: Bool{
return isResponder

and set its isResponder property to true in the end of viewDidLoad. Voila, my titleField was first responder.

Any time you’re finding yourself tieing your code into knots just to achieve something, you’re probably going about it the wrong way. Most Cocoa coding is relatively straightforward; making a designed-to-be-interactable class temporarily nonresponsive before sneaking into the getter of a read-only variable by a back door is NOT a good way forward. Don’t do it.

Attempt #3:
Create an NSWindowController subclass, associate it with the main window, and use the following in windowDidLoad()

let viewController = self.contentViewController as? ViewController

window?.initialFirstResponder = viewController?.title

This seemed to work in one instance, and then failed in the next. The ‘who is my first responder’ behaviour mirrors the Attempt #2. This can be observed in the screenshot: using makeFirstResponder means the correct field is logged as first responder, but the cursor is in a different field.

There’s an ‘initialFirstResponder outlet in IB, but you cannot hook it up with items in different viewController scenes. In the good old days of nibs, this worked; but with storyboards, nada.

(Apple’s documentation uses manual memory management in Objective C. I’ve lost track of the exact passage, but I found the actual example less than useful.)

Otherwise, I found a lot of advice to set initialFirstResponder in viewWillAppear. Which I had.

Eventually – and I can no longer find the source, I read A LOT of articles yesterday – someone mentioned window restoration in passing.

it turns out that the isRestorable property of NSWindow is checked by default, and if isRestorable is set to true, the first responder of your window will be whatever the first responder was when the user quit the application.
It will NOT show this behaviour if window restoration is turned off globally in SystemPreferences.

And to add a cherry on top: build schemes have a ‘launch application without state restoration’ setting; which makes four places that you need to check:

window.isRestorable in code
Behavior: Restorable in IB
launch application without state restoration in the build scheme
close windows when quitting an app in System Preferences

If window restoration is turned off by whatever means, setting initialFirstResponder or using makeFirstResponder in ViewWillAppear work like a dream. There’s still something slightly fishy here: both have the cursor in the desired control, but makeFirstResponder logs the control itself as first responder, initialFirstResponder does not; I expected both methods to have the same result.

Further reading on state restoration:

That is an iOS tutorial, but it’s worth following up on this.
deals with Resume and AutomaticTermination in Lion – macOS 10.7; turns out that isRestorable has been default since 2011… and I never noticed. (It is not fully implemented; it works for quitting applications, but not for closing the application’s window.)

Right now, this technology is not my priority, but ‘first introduced in 2011’ says everything you need to know about whether you should adapt it in your own app. There are apps where this is not necessary – my menu bar app with a fixed window size, for instance – but this is behaviour users expect, and 2011 to now is long enough that one should adapt this technology.

I don’t think I have ever heard it mentioned in any kind of depth in all of my years of reading programming books and articles and watching videos. Preserving state on iOS is a topic that gets mentioned more frequently – this is the nature of the mobile device – but for desktop, the most I’ve heard was ‘save the state of your app to NSUserPreferences’.

Lesson learnt:
My instincts about ‘I’m doing this right, I’m just missing something’ were spot on: I *had* missed the ‘window restoration’ layer of this problem. Given how useful window/app restoration is, I will roll with it (eventually), and operate on the assumption that I do not know which field my user has made first responder, and I should not care. This becomes a UI issue, but now I am warned, and I can plan ahead.