TextKit 2: The Promised Land
Marcin Krzyżanowski (Mastodon):
TextKit 2 (NSTextLayoutManager) API was announced publicly during WWDC21, which is over 4 years ago. Before that, it was in private development for a few years and gained widespread adoption in the macOS and iOS frameworks. Promised an easier, faster, overall better API and text layout engine that replaces the aged TextKit 1 (NSLayoutManager) engine.
[…]
Based on my 4 years of experience working with it, I feel like I fell into a trap. It’s not a silver bullet. It is arguably an improvement over TextKit 1. I want to discuss certain issues that make the TextKit 2 annoying to use (at best) and not the right tool for the job (at the worst).
[…]
Bugs in software are expected, and for TextKit 2, it’s no exception. I reported many bugs myself. Some issues are fixed, while others remain unresolved. Many users received no response. Additionally, bugs occur in specific versions, and regressions are common. It is annoying to maintain compatibility, of course. From my perspective, probably the most annoying bugs are around the “extra line fragment” (the rectangle for the extra line fragment at the end of a document) and its broken layout.
[…]
When ensuring layout only in the viewport (visible area), all other parts of the document are estimated. Specifically, the total height of the document is estimated. The estimation changes frequently as I lay out more/different parts of the document. […] The jiggery is super annoying and hard to accept. […] For a long time, I thought that I “hold it wrong” and there must be a way (maybe a private API) that addresses these problems, then I realized I’m not wrong. TextEdit app from macOS suffers from the very same issues I do in my implementations.
The API is designed for subclassing different pieces but doesn’t actually work if you try to do that.
Marcin is way further down the rabbit hole than I am, but I can certainly relate to his frustrations.
In fact, this is part of the reason I haven’t gotten further with Runestone for Mac. I hope to get further one day, but building a great text editor is incredibly time-consuming because you either add workaround upon workaround or you drop down to Core Text, which I did with Runestone for iOS/iPadOS.
Building stuff on closed source broken API is so incredibly frustrating.
It used to be worth it to provide the best native experience you can bring. But with the latest best of breed quality of first party apps not even knowing what that should mean it becomes futile.
I know if I would start now I would prioritize both cross platform and open source.
Previously:
- Notepad.exe 1.2.1139
- Swift Vision: Improving the Approachability of Data-Race Safety
- Runestone Rejected From the App Store
- Text Kit Benchmarks
- Chime Text Editor Now Open Source
- iOS 16 Text View Breakage
Update (2025-08-18): See also: Hacker News.
Is this why, in nvUltra and other editors, scrolling to the bottom of the page stops as if you’re at document end until layout catches up, and then the scrollbar extends and you can scroll further? Repeat ad nauseam until reaching the actual end of the document?
Ying Zhong (via via Nicolas Magand):
We built the core editor based on CodeMirror 6, we know what you’re thinking about: wow, that’s not native! However, we made the decision because we know text editing on macOS quite well, including TextKit 1 and TextKit 2, but still chose a web editor.
A simple fact is that TextKit is not better than contentEditable, the community doesn’t even have one single editor that can compete CodeMirror or Monaco. Implementing the same behavior using TextKit is desperately hard compared to web technologies, such as multi-caret editing, code folding, etc. TextKit 2 was introduced years ago, we don’t see good examples of leveraging it, the documentation and sample code from Apple are just too basic. Making a code editor is extremely hard, we are tired of exploring the darkness inside TextKit, but the open-source community of CodeMirror gives us much more confidence.
[…]
Also, we don’t just wrap a web editor with Electron and publish it as a “macOS app”, we use native WebView on macOS and rewrote UI controls to make the app truly delightful.
With NeXTstep it was exactly the opposite: you could usually rearrange, recompose, override the pieces any way you wanted, including from scratch, but the demos and pre-built parts were of such quality that you didn’t really want to.
But if you wanted: knock yourself out.
With the first version of Mac OS X, I was stunned that the high-level “convenience” APIs had capabilities that you couldn’t actually replicate using the lower level APIs. And this seems to be getting worse.
TextKit 2 is definitely reliable. It is used system-wide, including in Scrivener, for text fields (the small fields where you enter numbers or small chunks of info).
TextKit 2 is not yet ready for editing documents, though. You could use it for a plain ext editor or a coding app as long as you don’t require printing, but not for anything more complex. At the time of writing (November 2024), TextKit 2 is missing the following main features:
- Support for multiple containers and thus multiple pages. This means that it cannot be used for a page view such as Scrivener’s View > Text Editing > Show Page Layout option. It also means that it cannot be used for printing documents, since you need to allow for printing multiple pages when printing text, even if the editor doesn’t have a page view.
- Tables.
[…]
TextEdit is Apple’s TextKit showcase, and it uses TextKit 2 by default. However, if you insert a table, or if you choose Format > Wrap to Page, it switches to TextKit 1. It also uses TextKit 1 for printing.
Switching to TextKit 2 in @altstore introduced a bunch of bugs with no tangible benefits, all just to get rid of some deprecation warnings
I wish I’d just kept the deprecation warnings 😅
I think that Apple’s incentives are just not aligned with developers in niche segments - purely a matter of scaling and managing the teams.
In most cases open source alternatives are vastly better.
But the question remains, how can you finance the maintenance and evolution in the long term - volunteer work is not sustainable.
It is remarkable that after 25 years we still haven’t found a viable and scalable funding model for this.
Part of the reason the incentives aren’t aligned is that Apple isn’t dogfooding it. Yes, TextKit 2 runs part of TextEdit and the basic system text fields/views. But when Apple needs more features—Pages, Xcode, Notes—they don’t build on top of TextKit.
When I wrote @VintageText for my own daily Python programming use, I used TextKit 1 bc I had some existing code to start with. At the time I was worried it was a bad choice. (lol…writing yr own editor u’ve already made highly questionable choices). I guess it was a fine decision
Update (2025-08-19): Craig Hockenberry:
This is a TextKit rendering bug, and we moved over to the latest version to address these kinds of issues.
But it just exposed new ones.
Update (2025-08-21): Max Seelemann:
Why do we even give a shit? (Writing Tools + TextKit 2 „animation“ in TextEdit)
The slower you play it, the worse it gets.
And yes, that second paragraph becomes permanently transparent afterwards.
I believe you started the Ulysses journey at the perfect time in history, when AppKit was great, and text rendition on Mac beautiful without compare.
I am getting more convinced that this golden age is over and we need to prepare to having to leave :/
5 Comments RSS · Twitter · Mastodon
Same thoughts from the developer of the excellent MarkEdit here: https://github.com/MarkEdit-app/MarkEdit/wiki/Why-MarkEdit#we-do-things-correctly
I've lost weeks of life dealing with TextKit 1 bugs. I've been following devs like Marcin because I didn't have the heart to jump into TextKit 2 and get more burned.
I haven't found anyone who likes it.
Another data point, the dev of CotEditor reports similar "there are still high barriers that prevent CotEditor from migrating to TextKit 2". Unrelated, thanks Léo for ObjCSyntax.
TextEdit is the proxy I use to tell if TextKit 2 is "there yet". It's not (it's worse than when it was on TextKit 1). Apple's TextKit 2 example code is also broken. We're 4 years in.
What is Xcode using? What is Pages using? What's the TextKit 2 posterchild? When do the bugs get fixed? If the best thing is to drop to CoreText and write your own LayoutManager and TextView, what's the point of TextKit 2?
Text-heavy projects staying within Apple's stack face the choice:
Stay in the burning house of TextKit 1 (Apple has signaled we need to move to 2).
Jump into the lava pool of TextKit 2.
I think Apple's entire dev stack is a Promised Land.
@Hammer Haha, cheers.
I used to love digging into these semi private impls, figuring out their bugs and fixing them with swizzles. These days, that approach has become very hostile by using shitty runtimes such as Swift and SwiftUI, and even when possible, I just don’t have the patience or desire to do so, given the broader ecosystem and environment. When was the last time there was some amazing software? Everything these day, especially new software, is a subscription service with a terrible custom UI. At least we had Apple’s software to look forward to, provided good UI and UX. That is gone too. When Electron apps are more coherent than your dripping manure of a redesign, it’s just not worth the effort anymore.
If I remember correctly, Pages has its own completely bespoke layout system, that was unified years ago between macOS and iOS. If you have SIP disabled, you can attach with Xcode and take a look.
I have spent quite a few months working with TextKit 2 for editing rich text on Mac, although nothing in production yet, because it’s not up to scratch.
It didn’t support any kind of printing until macOS 15, which means no PDFs and no thumbnail generation. Now that it does, it’s not clear you can have control over pagination, as you can with TextKit 1. It doesn’t handle tables, which Writing Tools will generate, even though they recommend using TextKit 2 to get the animation when it rewrites text, ironically.
NSTextView will fall back to TextKit 1 if it encounters anything unsupported, and when it does you’ll instantly lose your view-based attachments. You never know what might trigger that. For example, a bug in macOS 14 and earlier meant showing a Quick Look preview of an attachment would force a fallback when it tried to generate the transition image. Some things (like that) you can work around, but the cases are not documented, and will vary from release to release. Unless what you’re doing is dead simple, it’ll probably trip you up one way or another.
TextKit 2 seems to be another of those things that doesn’t quite deliver what was promised, and while Apple made it clear it wasn’t complete back when it was announced, their progress in filling in the gaps has been glacial. It makes me worry, because I know there are great apps (much better than mine!) that perform wonders with TextKit 1. Clearly they want people to transition at some point, but right now they can’t. I can only hope they grapple it eventually, as they did with WKWebView.
This is exactly why we developed our own CoreText based textview for CodeEdit. We’d worked with Marcin on the STTextView project but even with his great work we were hitting problems we couldn’t optimize out, especially when loading large documents.
Our editor now loads million line files in milliseconds, supports multiple cursors, and does lazy layout so it avoids the issues others have mentioned while scrolling. We also support attachments, A11Y, drag and drop, and a ton more.
I think a core issue is that TextKit is really optimized for some specific niche of small content, and needing extreme flexibility layout-wise. It just doesn’t work well for code editors or even a large document editor that isn’t broken down into pages. In addition to that the TextLayoutManager API isn’t well designed for overriding everything, so customization or optimization of layout or internal structures is impossible.
Combine that with things like the fact that NSTextStorage is STILL not correctly bridged to Swift (meaning it can’t be subclassed efficiently) it means TextKit isn’t the choice for code editors.