Kristopher Baker iOS roots · Product systems · AI-assisted workflows
← Aoede

wip · 2026.06.29 · 2 min read

A TextKit 2 reader, for the scroll

Aoede's reading surface is built in SwiftUI, where every word is its own small view. That made the signature effects easy to attach in place, but it is the wrong shape for a long chapter. A few thousand words means a few thousand views to lay out, and the follow-scroll has to do an awkward dance: because off-screen positions are not known in a lazy list, the active line is kept centered with a settle pass and a recovery task that re-checks where things landed. It works, but it is the kind of structure that fights you as the book gets longer.

So in a worktree I rebuilt the reader on TextKit 2, hosting an NSTextLayoutManager in UIKit and AppKit under the same SwiftUI signature. The whole document becomes one attributed string drawn in a single fragment pass, and TextKit resolves off-screen rectangles directly, which deletes the settle-and-recover dance entirely. The reading highlight recolors through TextKit rendering attributes with no re-layout, and the karaoke fill and focus-lens glow redraw only the active word's rectangle off a display link. The two GPU effects, shimmer and wave, moved to a CAMetalLayer overlay aligned to the real layout fragments. Everything that makes the reader Aoede's had to come along: furigana ruby with reserved line headroom, hidden known words, the dictionary popover, find-in-book, and every gesture.

It is still an experiment. The whole thing sits behind a Labs toggle so I can A/B the two surfaces on the same book and the same playback, which is the only honest way to judge it. The Metal effects are device-unverified because the Simulator's Metal is limited, and I have not yet sat down and measured the frame-rate win rigorously. The bet here is structural rather than proven: a single laid-out text object should scroll a long book more smoothly than a few thousand individual views, and I want to feel that before I decide whether it replaces the SwiftUI reader or stays a fork.