swiftcocoanstextfieldfirst-responder

How do I manually set the key-view in a cocoa app?


I have a cocoa app written in Swift. The app has a number of NSControls for user entries. The user can use the tab key to move between the controls, most of them being textfields. I am doing all in Code, I do not use the Interface Builder.

Some of them are dynamic: I have a custom NSControl, let's call it myRow which itself is hosting two ´NSTextFields´. myRow itself is hosted in a superView, let's call it myRowManager. When the user makes an entry this myRow notifies the myRowManager, and another myRow is placed below the first one. When one field's content is deleted by the user the myRowManager then deletes that myRow and re-arranges the remaining myRows. The whole setup is mimicking a table view, where there is always one more row than there are entries so that the user can always add an entry at the end.

Using the tab key when modifying existing entries works just fine.

But the nature of my setup means, that when a row is added or deleted a method .layoutSubviews() is called, there all myRows are removed from the superView (the manager) and then new myRows are created, reflecting the new number and order, which is of course reflecting the underlying data.

But by removing the myRow which just had the focus from the view hierarchy sets the window's initial responder as the key-view, as the current and next element in the key-view loop are no longer present.

I therefore am trying to set the key-view manually to the newly created textfield. To do this, a reference to this textfield is kept by the layoutSubviews method.

Unfortunately I have not been successful so far.

What I am doing now (this is the method of the manager which is called by a myRow when an entry is added):

func rowAddedAtEnd(_ index: Int) {
    layoutSubviews()
    self.window?.recalculateKeyViewLoop()
    self.window?.selectKeyView(following:targetView)
}

What strikes me as odd is that there is no method th set a specific view as the key view. I can either use .selectKeyView(following:) or (preceding:)

But anyway - it does not work. The focus ring just gets reset to the top left element in the window.

How can I solve this?


Solution

  • From the documentation of func selectKeyView(following view: NSView):

    Sends the nextValidKeyView message to view and, if that message returns an NSView object, invokes makeFirstResponder(_:) with the returned object.

    To select targetView, skip nextValidKeyView and call makeFirstResponder(_:).

    self.window?.makeFirstResponder(targetView)