How is TextKit structured, how do TextKit 1 and 2 differ, and how do UILabel, UITextView and SwiftUI Text relate?

9.6.1 · L5 · Interview

Predict first

Does a new text view use TextKit 2, and what happens when you read its layoutManager?

import UIKit

let tv = UITextView()
print(tv.textLayoutManager != nil)
_ = tv.layoutManager
print(tv.textLayoutManager == nil)
  • true, then true
  • false, then true
  • true, then false

Predict first

What does a TextKit 1 layout manager report for text in a 60-point-wide container?

import UIKit

let st = NSTextStorage(string: "Hello world, this wraps")
let lm = NSLayoutManager()
let tc = NSTextContainer(size: CGSize(width: 60, height: CGFloat.greatestFiniteMagnitude))
st.addLayoutManager(lm)
lm.addTextContainer(tc)
lm.ensureLayout(for: tc)
let used = lm.usedRect(for: tc)
var lines = 0
lm.enumerateLineFragments(forGlyphRange: NSRange(location: 0, length: lm.numberOfGlyphs)) { _, _, _, _, _ in lines += 1 }
print(used.width <= 60, lines > 1)
  • true true
  • false true
  • true false

Mental model. The storage is the manuscript, the layout engine is the typesetter, and the container is the page shape. TextKit 2 sets only the pages you are about to see; TextKit 1 can set the whole book.

TextKit separates three roles. The text storage (NSTextStorage, an NSMutableAttributedString) holds the characters and attributes (font, colour, paragraph style, attachments). The layout engine turns them into positioned lines and glyphs. The text container (NSTextContainer) defines the region the text flows into (a width, a height, exclusion paths for text wrapping around shapes, a line-fragment padding and a maximum line count). In TextKit 1 the layout engine is NSLayoutManager, which works with glyphs and can answer usedRect(for:), line-fragment enumeration and glyph-to-character mapping. TextKit 2 (iOS 15+, the default for UITextView, UITextField and labels on recent systems) uses NSTextLayoutManager and NSTextContentManager, works with text elements and layout fragments instead of glyphs, lays out lazily (so very long documents are fast), and handles complex scripts and non-contiguous layout better. Its API is different: no glyph indexes, and viewport-based layout.

The two are not mixed: a UITextView uses TextKit 2 by default, and the moment you access its layoutManager property it silently falls back to TextKit 1 (and textLayoutManager becomes nil), which is a common surprise that changes performance and behaviour. To opt in deliberately, create the view with usingTextLayoutManager: false. UILabel and UIButton use the same engine through simpler APIs: they measure with intrinsicContentSize and wrap by their width (numberOfLines = 0 and preferredMaxLayoutWidth or constraints). To measure text yourself, use boundingRect(with:options:context:) with .usesLineFragmentOrigin, or a layout manager with a container sized to your width. SwiftUI Text is a separate, value-based API that uses the same text system underneath, supports attributed strings, Markdown, interpolation and Dynamic Type, and for editing you wrap a UITextView or use TextEditor. Always use Dynamic Type fonts (UIFont.preferredFont(forTextStyle:), adjustsFontForContentSizeCategory) and test long text, right-to-left languages and large sizes.

A new text view starts on TextKit 2. Touching the TextKit 1 layout manager switches it over, and the TextKit 2 manager is gone.

The text wraps inside the container, so the used width stays within 60 points and it takes several line fragments.

Common traps

  • Reading textView.layoutManager “just to look”. It switches the view to TextKit 1 for good; use textLayoutManager.
  • Measuring text with a different width or options than will be used. The wrapped height differs; measure with the real container width.

They might follow up with

  • How do you keep text legible for everyone? Dynamic Type styles, adjustsFontForContentSizeCategory and layouts that grow.
  • What is an exclusion path? A shape the text flows around inside the container.

Related questions

  • 9.5.1 How do you draw with Core Graphics and UIBezierPath, and how do scale and caching affect it?
  • 9.1.1 How does Auto Layout work, and what are intrinsic size, content hugging, compression resistance, priorities…