TextKit 2 Example App from the Apple Docs

The Apple Developer Docs have an example app, “Using TextKit 2 to Interact with Text”. That’s related to WWDC 2021’s introduction to TextKit 2.

Availability:

  • iOS 15.0+
  • iPadOS 15.0+
  • macOS 12.0+
  • Xcode 13.0+

So it’s usable on macOS Monterey and the latest iOS.

I ran the demo app and resized the window a bit. It’s buttery smooth, and that’s good.

But the butter, in part, comes from each block seeming to move up/down independently. Spring-loaded, so to speak. You can see this in the following demo when I move the mouse rather quickly:

When multi-line paragraphs occupy many more or far fewer lines than they did before, the headings between these paragraphs move at different speeds, then ease-out as they catch up.

I believe the intention here is to show what a block or text fragment is, and that you can animate them individually. TextKit 2 is block-based; so you have a paragraph block, and headings blocks. These are laid-out, eh, en bloc. When you turn on the turtle mode in the toolbar, the ease-out animation is even more pronounced.

Most of this is visible in TextDocumentView.swift:

func textViewportLayoutController(
        _ controller: NSTextViewportLayoutController,
        configureRenderingSurfaceFor textLayoutFragment: NSTextLayoutFragment) {
    // (1) Tuple-returning API
    let (layer, layerIsNew) = findOrCreateLayer(textLayoutFragment)
    if !layerIsNew {
        let oldPosition = layer.position
        let oldBounds = layer.bounds
        layer.updateGeometry()
        if oldBounds != layer.bounds {
            layer.setNeedsDisplay()
        }
        if oldPosition != layer.position {
            // (2) Layer animation
            animate(layer, from: oldPosition, to: layer.position)
        }
    }
    if layer.showLayerFrames != showLayerFrames {
        layer.showLayerFrames = showLayerFrames
        layer.setNeedsDisplay()
    }

    contentLayer.addSublayer(layer)
}
  1. The findOrCreateLayer method is interesting because the Apple developer(s) who wrote this sample return a tuple. That’s refreshingly new. But that’s not TextKit 2 API, just part of the TextDocumentView.

  2. The layer is of type TextFragmentLayer, which is a CALayer subclass. So you can animate each NSTextLayoutFragment independently.

Here’s another video showing slow-motion with layer borders turned on. You can see how the layers move until they touch:

It’s interesting, to say the least, that layout fragments or blocks will be laid-out with independent CALayers or NSView. I’m not certain if that’s actually true, to be honest, but this seems to be the implication when you mess with NSTextViewportLayoutController, because the docs for the delegate method implementation from above is:

The delegate presents the text layout fragment in the UI, for example, in a sublayer or a subview. Layout information such as viewportBounds on textViewportLayoutController isn’t up to date at the point of this call.

I need to actually experiment with TextKit 2 to get any understanding of the components at all. Wanted to share my surprise about CALayer usage here, though.

When you remove the delegate method, by the way, there won’t be any laid-out content. So either the TextKit 2 demo is demonstrating very low level interaction or making text editors of the future will just be different.

Will keep you posted when I find out more.