Sample CodeiOS, iPadOS, Mac Catalyst, macOSReviewed 2026-07-21View on Apple Developer

Using TextKit 2 to interact with text

At a glance

Item Summary
Purpose Build a custom TextKit 2 viewport renderer with selection, nested comments, and shared iOS/macOS layout fragments.
App architecture Each platform controller constructs NSTextContentStorage, NSTextLayoutManager, and NSTextContainer; its custom TextDocumentView becomes the layout/viewport delegate, maps fragments to layers, and substitutes shared BubbleLayoutFragment instances for comment paragraphs.
Main patterns Explicit TextKit object graph, Viewport rendering adapter, Layout fragment specialization, Parallel platform shells
Project style Fifteen Swift files split into iOS, macOS, and Shared groups; platform controllers/views surround shared fragment and reaction code.

Project structure

Source bundle/
├── iOS/
│   ├── AppDelegate.swift
│   ├── TextDocumentView.swift
│   ├── CommentPopoverViewController.swift
│   ├── SceneDelegate.swift
│   └── TextDocumentViewController.swift
├── macOS/
│   ├── AppDelegate.swift
│   ├── TextDocumentView.swift
│   ├── CommentPopoverViewController.swift
│   ├── TextDocumentViewController.swift
│   └── WindowController.swift
└── Shared/
    ├── BubbleLayoutFragment.swift
    └── CommentUtils.swift

Structure observations

  • The TextKit graph is assembled explicitly in each platform controller rather than hidden in app delegates.
  • The custom document view is both the viewport rendering adapter and interaction surface for selection/comments.
  • Shared fragment/layer types contain drawing geometry; iOS and macOS files contain platform input, scrolling, and presentation behavior.

Overall architecture

Reference code

iOS/TextDocumentViewController.swift:29 — explicit TextKit 2 object graph

    required init?(coder: NSCoder) {
        textLayoutManager = NSTextLayoutManager()
        textContentStorage = NSTextContentStorage()
        super.init(coder: coder)
        textContentStorage.delegate = self
        textContentStorage.addTextLayoutManager(textLayoutManager)
        let textContainer = NSTextContainer(size: CGSize(width: 200, height: 0))
        textLayoutManager.textContainer = textContainer
    }

Interpretation

Text storage, layout, and rendering are distinct owners. The controller assembles content/layout services and supplies display attributes; the custom view owns viewport and interaction mechanics. Shared fragment subclasses and CALayers turn TextKit layout results into specialized drawing without making controllers render text directly.

Ownership and state

Ownership evidence

iOS/TextDocumentView.swift:141 — view-local rendering state and delegates

    private var contentLayer: CALayer! = nil
    private var selectionLayer: CALayer! = nil
    private var fragmentLayerMap: NSMapTable<NSTextLayoutFragment, CALayer>
    private var padding: CGFloat = 5.0

    var textLayoutManager: NSTextLayoutManager? {
        didSet {
            textLayoutManager?.delegate = self
            textLayoutManager?.textViewportLayoutController.delegate = self
            layer.setNeedsLayout()
        }
    }
Owner Object or state Relationship Mutation authority
Platform TextDocumentViewController Content storage and layout manager Creates private instances during initialization and connects them It loads source text, controls comment visibility, and supplies paragraph display attributes.
NSTextContentStorage / NSTextLayoutManager Text elements, layout, container, selections Linked through TextKit’s manager relationship TextKit drives layout and editing transactions.
Platform TextDocumentView Content/selection layers, weak fragment-layer map, padding Creates and privately retains rendering state It manages viewport layout, selection visuals, scrolling, and comment insertion.
Viewport content layer TextLayoutFragmentLayer sublayers Retains visible rendering layers The view clears/repopulates them around each viewport layout pass.
TextLayoutFragmentLayer One layout-fragment reference and drawing geometry Retains the fragment used for drawing It updates layer bounds/position and delegates glyph drawing to the fragment.

Composition denotes source-visible construction and retained value state. Aggregation denotes a stored or injected collaborator whose exclusive lifetime the source does not prove.

Class and protocol design

iOS/TextDocumentView.swift:251 — comment-specific layout fragment selection

    func textLayoutManager(_ textLayoutManager: NSTextLayoutManager,
                           textLayoutFragmentFor location: NSTextLocation,
                           in textElement: NSTextElement) -> NSTextLayoutFragment {
        let index = textLayoutManager.offset(
            from: textLayoutManager.documentRange.location, to: location)
        let depth = textContentStorage!.textStorage!
            .attribute(.commentDepth, at: index, effectiveRange: nil) as! NSNumber?
        if let depth {
            let fragment = BubbleLayoutFragment(
                textElement: textElement, range: textElement.elementRange)
            fragment.commentDepth = depth.uintValue
            return fragment
        }
        return NSTextLayoutFragment(textElement: textElement,
                                    range: textElement.elementRange)
    }
Type Responsibility Depends on or conforms to
Platform TextDocumentViewController Builds TextKit graph and supplies comment display/presentation policy Content-storage manager delegates plus UIKit/AppKit controller roles
Platform TextDocumentView Viewport rendering, interaction, selection, scrolling, comment insertion Viewport/layout-manager delegates and platform input callbacks
BubbleLayoutFragment Adds indentation, margins, bubble geometry, and background drawing NSTextLayoutFragment subclass
TextLayoutFragmentLayer Adapts a TextKit layout fragment to a CALayer rendering surface CALayer subclass
Reaction / comment attribute Shared comment metadata and reaction presentation values Shared enum and NSAttributedString.Key extension

All protocol conformances are TextKit/UIKit/AppKit framework contracts. The sample defines no local protocol abstraction; extensibility comes from framework delegates and subclassing NSTextLayoutFragment/CALayer.

Access control

Symbol Access Verified effect Design reason
Controller content storage, layout manager, current fragment, comment flag private Only the controller’s lexical implementation can mutate the TextKit session Prevents popover or view code from bypassing controller coordination.
View content/selection layers, map, padding, rendering helpers private Confined to each platform view implementation Protects viewport and layer-cache invariants.
Bubble geometry/color/path helpers private Only BubbleLayoutFragment drawing can use them Keeps drawing calculations behind fragment overrides.
NSAttributedString.Key.commentDepth public static Exported with the Foundation key extension The shared semantic attribute can be referenced across targets/modules that include the file.
Fragment/view/controller types implicit internal Visible within their app target/module They are sample implementation types rather than public framework API.

Reference code

Shared/CommentUtils.swift:10 — shared public attributed-string key

extension NSAttributedString.Key {
    public static var commentDepth: NSAttributedString.Key {
        return NSAttributedString.Key("TK2DemoCommentDepth")
    }
}

enum Reaction: Int, CaseIterable {
    // Reaction metadata remains module-internal.
}

The attribute key is the one explicit exported symbol; enclosing sample classes remain internal and implementation state is private. No open or fileprivate declaration is used in the shared rendering path.

Logic ownership and placement

Logic Owning type or file Why it lives there
TextKit graph construction and source loading Platform TextDocumentViewController It owns the content/layout objects and platform presentation lifecycle.
Viewport layer creation, reuse, geometry, selection Platform TextDocumentView These decisions depend on scrolling/input coordinates and visible layout fragments.
Comment fragment choice TextDocumentView layout-manager delegate It reads the comment-depth attribute at the requested text location.
Bubble drawing geometry Shared BubbleLayoutFragment The subclass owns rendering-surface bounds and drawing overrides.
Fragment-to-layer rendering Shared TextLayoutFragmentLayer It translates TextKit fragment geometry into Core Animation state.

Design patterns

Pattern Source evidence Purpose or tradeoff
Explicit TextKit object graph iOS/TextDocumentViewController.swift:29 Makes storage, layout manager, and container ownership visible and configurable.
Viewport rendering adapter iOS/TextDocumentView.swift:61 Creates/reuses one layer per visible layout fragment during viewport passes.
Layout fragment specialization Shared/BubbleLayoutFragment.swift:15 Adds comment layout/drawing without branching inside the generic layer renderer.
Parallel platform shells macOS/TextDocumentView.swift:17 and iOS/TextDocumentView.swift:17 Shares rendering primitives while keeping platform scrolling and input idiomatic.

Naming conventions

  • TextKit roles retain framework vocabulary (TextContentStorage, TextLayoutManager, TextLayoutFragment).
  • Custom types add one responsibility (BubbleLayoutFragment, TextLayoutFragmentLayer, TextDocumentView).
  • Private commands describe mechanics (findOrCreateLayer, updateSelectionHighlights, adjustViewportOffset).
  • Platform folders intentionally reuse primary type names because each target compiles only its own shell.

Architecture takeaways

  • Construct the TextKit storage/layout/container graph at the controller boundary and pass collaborators to the rendering view.
  • Use the viewport delegate to materialize only visible fragment layers and a fragment subclass for specialized layout/drawing.
  • Keep platform interaction shells separate while sharing fragment and layer primitives.
  • Reserve public access for a true shared semantic key; keep rendering state private.

Source map

Source file Architectural role
iOS/TextDocumentViewController.swift iOS TextKit graph, comment visibility, and popover presentation
iOS/TextDocumentView.swift iOS viewport rendering, selection, scrolling, and comment interaction
macOS/TextDocumentViewController.swift macOS TextKit graph and document-controller policy
macOS/TextDocumentView.swift macOS viewport rendering, selection, scrolling, and mouse interaction
Shared/BubbleLayoutFragment.swift Comment layout and bubble drawing
Shared/TextLayoutFragmentLayer.swift Fragment-to-Core-Animation adapter
Shared/CommentUtils.swift Shared comment attribute and reaction metadata