Typing in the nook
The nook lives in a panel that never activates your app. The app the person was
using stays in front, keeps its menu bar, and gets the keyboard back as soon as the
person is done typing into the nook. OpenNook handles the parts of that a text input
needs, so a TextField in home content or a companion works without extra code.
What happens by default
Section titled “What happens by default”- A click gives the nook the keyboard. A click anywhere in the nook makes typing go to it, so keyboard shortcuts in your content work after a click. A click on a text input always does, even after the person has been in another app.
- Another app takes it back. When the person clicks into another app, the
keyboard goes with them and the nook’s text inputs give up focus:
@FocusStateturnsfalse, and anything tied to it lets go. - Collapsing hands it back. When the nook collapses or hides, typing goes back to the app in front, so keys never disappear into a collapsed nook.
- Editing shortcuts work. Command-X, C, V, A, Z, and Shift-Command-Z come from an app’s Edit menu. A notch app shows no menu bar and often has no main menu, so OpenNook installs a hidden Edit menu at launch when the app has none of its own.
Holding the nook open while a field has focus
Section titled “Holding the nook open while a field has focus”A person typing may move the pointer off the nook, which would collapse it. Hold it open while the field has focus:
struct ReplyField: View { @State private var reply = "" @FocusState private var isFocused: Bool
var body: some View { TextField("Reply", text: $reply) .focused($isFocused) .nookKeepsExpanded(whileFocused: $isFocused) }}The hold lasts while the field has focus and the nook has the keyboard, and ends shortly after either goes - including when the person clicks into another app. If the pointer left the nook during the hold, the nook then closes.
Focus alone is not enough because SwiftUI can focus a field when content first
appears, before the person has clicked into the nook; typing then still goes to the
app in front. @Environment(\.nookHasKeyboardFocus) tells the two apart, for
example to show a caret or a hint only while typing would reach the field.
Focusing a field when it appears
Section titled “Focusing a field when it appears”A field that appears because the person asked for it - a reply field a button
reveals - should take typing straight away. Setting its FocusState from
onAppear, task, or defaultFocus does not focus a field that has just appeared
in the nook, and typing then goes nowhere. Use nookFocusOnAppear(_:), which gives
the nook the keyboard and focuses the field once it is in place:
struct ReplyField: View { @State private var reply = "" @FocusState private var isFocused: Bool
var body: some View { TextField("Reply", text: $reply) .focused($isFocused) .nookFocusOnAppear($isFocused) .nookKeepsExpanded(whileFocused: $isFocused) }}Don’t put it on a field that shows whenever the nook opens: hovering the nook would then take the keyboard from the app the person is typing in.
Typing without a click
Section titled “Typing without a click”To take the keyboard at any other time - a key handler that should work as soon as
the nook opens, say - ask for it from a view with
\.nookChromeActions.takeKeyboardFocus().
Outside chrome content, call coordinator.takeNookKeyboardFocus() (it returns
false while the nook is hidden) and read coordinator.nookHasKeyboardFocus. Hand
the keyboard back yourself - after sending a message, say - with
chromeActions.releaseKeyboardFocus() or coordinator.releaseNookKeyboardFocus(),
which also ends editing in the focused field.
At the engine level, Nook has the same calls: takeKeyboardFocus(),
releaseKeyboardFocus(), and the published hasKeyboardFocus.
The global shortcut
Section titled “The global shortcut”By default the show/hide shortcut only shows the nook; typing still goes to the app in front until the person clicks. To type straight away, opt in:
configuration.chromeBehavior.keyboard.shortcutTakesKeyboardFocus = trueWhen the shortcut opens the nook, the nook then takes the keyboard, and its focused
text input takes typing at once. SwiftUI focuses a text input in the nook by itself
when the nook gets the keyboard, so a nook with one field needs nothing more; with
several, set the one you want through its FocusState. Opening the nook any other
way leaves the keyboard alone.
The Edit menu
Section titled “The Edit menu”The hidden Edit menu (NookEditMenu) is added only when no item in the app’s main
menu already pastes, so a host with its own Edit menu keeps it. Its items have no
target, so each shortcut goes to whichever text input has focus. To install your own
menu instead, turn it off:
configuration.chromeBehavior.keyboard.installsEditMenu = falseReaching the panel
Section titled “Reaching the panel”For window-level work OpenNook has no API for, coordinator.nookWindow (or
Nook.window) is the panel right now, nil while the nook is hidden. Read it when
you need it rather than keeping it: the nook builds a new panel when it shows after
being hidden and when it moves to another display. Its accessibility identifier is
opennook.panel.
Pitfalls
Section titled “Pitfalls”- Don’t activate the app to type.
NSApp.activatepulls your app to the front and takes the menu bar from the person’s app. Taking the keyboard focus is enough. File pickers are the exception, andNookFilePickeralready handles them; see File pickers from a module. - Keys typed while the nook has the keyboard but no field has focus go nowhere. Focus a field when you take the keyboard, or hand it back.
See also
Section titled “See also”- Companion surfaces - text inputs and holds in companions.
- Chrome customization -
where
chromeBehavior.keyboardlives beside the other behavior knobs.