Me: How can you wrap NSTextView events to go through a ReSwift store? ChatGPT: It’s not clear what you’re asking. Could you please provide more information or context? I’m a large language model trained by OpenAI, so I don’t have the ability to browse the internet or access information outside of what I was trained on. I can provide general information and advice based on that training, but I may not be able to answer specific questions about events in NSTextView or ReSwift stores.
For a plain text (not rich text/RTF) NSTextView, I found that: Since NSTextView doesn’t understand the NSPasteboard.PasteboardType.string pasteboard type for reading or writing, I tried two approaches to handle plain text input (pasting) and output (cut/copy): The backport seems to work, but extending supported types sounds like more robust solution.
When you work with NSTextView and happen to use insertText(_:) to programmatically insert text, you get an undoable action for free. This might give the impression you get undo/redo functionality for free. Eventually, you’ll notice how other changes don’t have an affordance in the “Edit” menu. While it’s possible to get “Undo Typing” and “Undo Set Color” from some function calls, it’s not possible to get “Undo Change Text Attributes” when you use NSTextStorage.addAttributes(_:range:).
In short: Image file drag and drop does only produce file URLs, either with security scope-able bookmarks or plain file paths. Dragging an image file from Finder onto an NSTextView will trigger performDragOperation(_:). You get access to NSDraggingInfo there and can inspect available content. Its draggingPasteboard (shortened to pb here) contains the following data when executed on macOS 12 Monterey:
I was profiling performance bottlenecks in the The Archive and noticed that no matter how much highlighting functionality I removed/commented-out, the apparent slowness was all due to … Touch Bar API?! I have been “pruning” the Touch Bar related calls from the profiling stack to focus on what I though would be the real bottlenecks. But, as often, it turns out this was stupid and the instruments did point out the true problem. Something indeed was causing trouble here, it turned out.
In The Archive, people relying on character composition to enter their text noticed that the auto-saving routing got in the way and aborted the composable editing mode. This affects e.g. Chinese or Japanese character input on macOS, but also when you hit a composable accent like ´ after which the text editor waits for another character to put underneath the accent.
By default, NSTextView will show the NSSharingServicePicker button when you hover over an image inside the text view. That’s true even for custom image-based NSTextAttachments in the attributed string. The default menu item is limited to “Markup” and a “Services” submenu, I believe. Apps can register to be shown in this menu, and users can customize the menu in System Preferences.
If you ever wondered how to programmatically trigger a click on a link in a NSTextView, here’s one way to do so. This assumes that clickable links are not stored as temporary attributes in the NSLayoutManager, but permanently as part of the “model” in your NSTextStorage. You can then ask the storage for the attribute at the cursor/insertion point location:
Rich Siegel recently wondered on Slack why NSTextView would suddenly display empty placeholders for some glyphs when the font does not support them, instead of falling back to a safe font like it usually does. Chinese characters never got displayed. Michel Fortin remembered a similar problem, and the potential fix was quite simple:
In summary, if you have to change the font after the text storage was edited, do it in willProcessEditing and it’ll do the right thing. Don’t do it in didProcessEditing.
That turned out to be what tripped up Mr Siegel’s text view, which now happily displays CJK/CJKV again. For more details and some background about how you can detect this problem in your apps, read Michel’s post.
Today I learned why my NSTextField permits pasting of newline characters even though I set usesSingleLineMode properly. It’s because I made it conform to NSTextViewDelegate to cache changes. When you edit text inside of an NSTextField, you actually type inside a field editor of the window. That’s a shared NSTextView instance. Most of the hard work of an NSTextField is done by its cell, which is an NSTextCell. NSTextCells implement at least the delegate method NSTextViewDelegate.textView(_:shouldChangeTextIn:replacementText:) – and when you set usesSingleLineMode, this is actually set for the cell, not the view itself. You can use textView(_:shouldChangeTextIn:replacementText:) to sanitize input text, and I suspect that’s where the usesSingleLineMode implementation happens. If your NSTextField subclass implements this method, the NSTextCell implementation isn’t called. And since that one isn’t public (it was called “implicit protocol conformance” back in the day), you cannot delegate up in Swift because the compiler knows it isn’t there.
To improve my note editing app The Archive’s responsiveness, I want to find out when the user is actively editing a note. Here’s how to create the idle switch in RxSwift/RxCocoa. A simple enum of .typing and .idle will do for a start. Of course, NSTextViewDelegate provides a textDidChange(_:) callback to hook into; it’s based on a notification, so you can also subscribe to the NSText.didChangeNotification directly if you don’t want to use a delegate. That’s the input signal I’m going to use.
NSTextView can be customized to display different colors for the insertion point (aka caret, aka text cursor) and for selected text. This is especially useful when you add themes to your editor and the default settings don’t fit anymore. The default values are not exposed anywhere, so I had to log them and reconstruct convenient accessors to reset a text view if needed:
For whatever reason, my current app project’s find bar does not make the text view firstResponder again when you hit Escape or click the “Done” button to close it. This is very uncomfortable for users: they type away, hit ⌘F to find a phrase, then hit Esc – and now they’re in limbo. To my astonishment, the NSTextFinderAction called hideFindInterface is not triggered when you make the find bar disappear. Its opposite, showFindInterface, is triggered when the find bar slides back in, though. Intercepting in NSTextView.performTextFinderAction(_:) does not help, then.
On iOS, this does maybe not happen at all, but when you want to write syntax highlighting code for macOS apps, copying together stuff from around the web, you’ll end up with broken application behavior. In short: when you type and the attributes of the line change, the insertion point is moved to the end of the line. That sucks.
I have no clue why my previous attempts at customizing drawInsertionPoint(in:color:turnedOn:) always produced visual glitches. I really tried a lot of different ways. But it turns out you don’t have to do that much, really: Adapted to Swift from a Gist by koenbok.
In the original post about a cheap way to set the line height in a text view to, say, 150%, the result kind of worked but didn’t look that cool. One issue is that the extra line spacing was exclusively added at the bottom. With the following solution, you’ll get a proper line height with tastefully aligned insertion point and baseline and all.
Typewriter modes depend on the feature that you can scroll farther up and down than usual. You need extra whitespace, most of the time in both directions. Let’s start with “overscrolling” to understand what we need. Regular text views show additional bottom whitespace only until you fill it with text. Take TextEdit, for example. You can start to type at the topmost edge of the text view and the rest of the window is blank.
NSTextView (and UITextView for that matter) have a defaultParagraphStyle attribute where you can set the text’s line height. That works swell – if you display text statically. Once the user can enter something, you can run into trouble: Update 2017-07:I posted a better version without paragraph style attributes that hooks into the NSLayoutManager delegate callbacks for a more consistent and speedy experience!