Thanks Ziqiao — this matches what I landed on. I ran into these same challenges building my own TextKit 2-based editor and ended up on exactly the display-attributes approach you describe (NSTextContentStorageDelegate.textContentStorage(_:textParagraphWith:) returning an NSTextParagraph with resolved attributes, backing store left untouched). It's solid and the per-paragraph performance is fine.
One thing worth adding for anyone finding this thread: the delegate is re-invoked automatically when a paragraph is edited, which is perfect for syntax highlighting (appearance is a function of the text). But if your highlight changes without a text edit — Dark Mode, Dynamic Type, a search/selection-driven highlight — nothing re-vends the paragraph and it goes stale. The fix that's been reliable for me is to change the input my resolver reads, then call invalidateLayoutForRange: on the affected range (scope it to the paragraph for per-keystroke changes; whole document is fine for rare global changes like trait changes). Notably, invalidateLayoutForRange: does re-trigger the content-storage delegate, even though invalidateRenderingAttributes(for:) doesn't redraw — which lines up with the rendering-attributes bug you confirmed; the layout-invalidation route is the one that works.
Full working example if it's useful: TextKit2-Editor:
see SXTextStorageDelegate (the vending) and handleTraitChange in SXTextViewController (the forced re-vend).
Topic:
UI Frameworks
SubTopic:
SwiftUI
Tags: