Nerdy internals of an Apple text editor 👨🏻‍🔧
Tags: ux, ui, ios, design
Published: 2024-03-04 13:41:35

> I tried my best to convert it from the [original article](https://papereditor.app/internals).
> 
> ⌘+Click on the **DEMO** links to view the videos.

---

In this article, we’ll dive into the details of the way [Paper](https://papereditor.app) functions as a `TextView`-based text editor for Apple platforms.

The [first article](https://papereditor.app/dev) was just a warm-up — here is where we get to truly geek out! 🤓

> Before we start, I’ll add that for the time being Paper is built on the older TextKit 1 framework, so the article is relative to TextKit 1. That said, all of the concepts, abstractions, and principles discussed here still exist in TextKit 2, either unchanged or under a better API.

## Text view

To understand how text editing works in native Apple text editors, we first need to discuss the centerpiece of the whole system — the `TextView` class. Technically, `NSTextView` and `UITextView` have their differences, but the API is similar enough that we can treat them as a single `TextView` class. I will highlight the differences where necessary.

`TextView` is a massive component that only [grows in complexity](https://papereditor.app/dev#gnarly-stuff) with each release of respective operating systems. The TextEdit app consists almost entirely of a single `TextView`. When a single class can be used to build an entire app — you know it’s a beast.

Luckily, `TextView` is not just one huge pile of code. Apple tried to subdivide it into a bunch of layers — each represented by a flagship class. The layers build on top of each other to create a text editing experience.

![A diagram showing the classes that make up the text view. NSTextStorage and NSTextContainer flow into NSLayoutManager which then flows into TextView. Finally, TextView flows into ScrollView. Each next class in the diagram uses the information from the previous one to, in the end, construct a complete text editor.](https://papereditor.app/internals-article/text-view-classes.png)

### `NSTextStorage`

- _Stores_ the raw text string.
- _Stores_ the attributes (string-value pairs) assigned to ranges of text.
  - Styles such as font and color (defined by AppKit and UIKit).
  - Any string-value pair that acts as metadata for your needs.
- Emits events about text and attribute changes.

### `NSTextContainer`

- Defines the shape and dimensions of the area that hosts text symbols (glyphs).
- Most of the time it’s a rectangle (duh 🙄) but can be any shape.

### `NSLayoutManager`

- Figures out the dimensions of the glyphs and the spacings between them by looking at the ranges of attributes applied to the text string in `NSTextStorage`.
  - Extracts vector glyphs from the font.
  - Converts each text character to one or more glyphs. Some symbols and languages need more than one.
  - Calculates the size of each glyph.
  - Calculates the distances between glyphs.
  - Calculates the distances between lines of glyphs.
- Lays out each glyph, line by line, into the shape defined by `NSTextContainer`.
  - Calculates where every line of text starts and ends.
  - Calculates how many lines there are and what is the total height of the text.

### `TextView`

- Draws the glyph layout generated by `NSLayoutManager`.
- Syncs the height of the view with the current height of laid-out text.
- Manages text input.
- Manages the _text selection_.
- Manages the _caret_ — empty text selection.
- Manages the _typing attributes_ — attributes applied to the newly inserted text.
- Can define margins (`textContainerInset`) around the `NSTextContainer`.
- Manages all the additional bells and whistles such as dictation, copy-paste, spell check, etc.

### `ScrollView`

- Shows the visible portion of the `TextView`.
- Manages scrolling, scroll bars, and zooming.
- Can define its own margins (`contentInset`) in addition to the - `textContainerInset` defined by the `TextView`.
- Implementation details:
  - **AppKit**
      - `NSScrollView` contains `NSClipView` and two instances of `NSScroller`.
      - `NSClipView` contains `NSTextView`.
      - Thus many separate classes work together to make the scrolling effect.
  - **UIKit**
      - `UITextView` extends from `UIScrollView`.
      - Thus `UITextView` holds everything, including the scrolling logic.
      - Another notable detail is that moving the caret outside the visible area of `UITextView`, bounded by `contentInset`, causes `UITextView` to _auto-scroll_ to ensure that the caret stays within the visible area. You can often experience this in iOS text editors, where if the caret moves behind the keyboard, the editor scrolls to the next line. This is because the bottom _contentInset_ is dynamically set to the current height of the keyboard.

![A diagram breaking down the interface of the Mac app. Areas of the interface are outlined with different colors to show what classes are responsible for them.](https://papereditor.app/internals-article/text-view-mac.png)


![A diagram breaking down the interface of the iPad app. Areas of the interface are outlined with different colors to show what classes are responsible for them.](https://papereditor.app/internals-article/text-view-ipad.png)

![A diagram breaking down the interface of the iPhone app. Areas of the interface are outlined with different colors to show what classes are responsible for them.](https://papereditor.app/internals-article/text-view-iphone.png)

## Attributes

With the general structure of `TextView` out of the way, let’s zoom in on `NSTextStorage`, or rather its parent class `NSAttributedString`, as it is the foundation of rich text editing in Apple’s frameworks.

`NSAttributedString` consists of two parts:

- A regular text string.
- String-value pairs of attributes attached to ranges of text within the string.

Attributes are used mostly for styling purposes, but nothing restricts you from assigning custom string-value pairs for your own needs.

To get started, let’s make an `NSAttributedString` via the API:

```objc
NSMutableAttributedString *string = [NSMutableAttributedString.alloc
  initWithString:@"The quick brown fox jumps over the lazy dog."];

NSMutableParagraphStyle *style = NSMutableParagraphStyle.new;
style.firstLineHeadIndent = 30.0;

[string addAttribute:NSParagraphStyleAttributeName
               value:style
               range:NSMakeRange(0, string.length)];
[string addAttribute:NSFontAttributeName
               value:[NSFont systemFontOfSize:25.0]
               range:NSMakeRange(0, string.length)];

[string addAttribute:NSForegroundColorAttributeName
               value:NSColor.brownColor
               range:NSMakeRange(10, 5)];

[string addAttribute:NSFontAttributeName
               value:[NSFont boldSystemFontOfSize:25.0]
               range:NSMakeRange(20, 5)];

[string addAttribute:NSBackgroundColorAttributeName
               value:NSColor.lightGrayColor
               range:NSMakeRange(26, 4)];

[string addAttribute:NSUnderlineStyleAttributeName
               value:@(NSUnderlineStyleSingle)
               range:NSMakeRange(35, 4)];
[string addAttribute:NSFontAttributeName
               value:[NSFontManager.sharedFontManager
                      convertFont:[NSFont boldSystemFontOfSize:25.0]
                      toHaveTrait:NSFontItalicTrait]
               range:NSMakeRange(35, 4)];
```

`NSRange` is a structure consisting of a `location` and a `length`. `NSMakeRange(10,5)` means a range of `5` characters starting from position `10`, or in other words, an inclusive range between positions `10` and `14`. In case different ranges define the same attribute under the same position then the last applied range takes precedence. In the example above, the bold and italic fonts overwrite the default font that is applied to the whole string.

This code can be easily visualized in TextEdit as it is pretty much an `NSTextView` with some buttons.

![TextEdit app window with the text “The quick brown fox jumps over the lazy dog.” The text is styled with different fonts, colors, and background colors. Every style is labeled with the names of attributes that are applied to it.](https://papereditor.app/internals-article/text-attributes-in-textedit.png)

The second big part of the API is dedicated to checking what attributes are applied to what ranges. The API itself is quite peculiar. A lot of thought has gone into making it fast and efficient, but as a result, the usage can be a bit of a pain.

For instance, if you want to check whether a certain attribute exists at a certain position you would use this method:

```objc
id value = [string attribute:NSFontAttributeName
                     atIndex:6
              effectiveRange:nil];
```

If the value is `nil`, then it does not exist. Otherwise, it is the value of the attribute which in this case is a `NSFont`/`UIFont` object. So this method can be used both to query the value and to check the existence of the attribute.

But it gets better. You can pass a pointer to the `NSRange` structure as the last argument (the good old C technique to return multiple values from a single function call):

```objc
NSRange effectiveRange;
id value = [string attribute:NSFontAttributeName
                     atIndex:6
              effectiveRange:&effectiveRange];
```

And it will return either:

- The range of the continuous span of the same attribute with the same value.
- Or the range of the gap where the attribute is absent.

![Two diagrams with the text “The quick brown”. The word “brown” is in brown color. In the first diagram, the NSFontAttributeName attribute is sampled at index 6. The result is “nil” and the effective range is between indexes 0 and 9 inclusive. In the second diagram, the NSFontAttributeName attribute is sampled at index 11. The result is an NSFont.brownColor object and the effective range is between indexes 10 and 14 inclusive.](https://papereditor.app/internals-article/text-attributes-at-index.png)

Though not exactly… You see the `effectiveRange` here is not what you think it is. Quoting the documentation:

> The range isn’t necessarily the maximum range covered by the attribute, and its extent is implementation-dependent.

In other words, it _could be_ the correct maximum range… but it also _might not be_.

“_Ahh — I just love having a bit of non-determinism in my code!_”

![Same diagram as the second diagram in the previous image except multiple different ranges are labeled. Labels read: “Can be this”, “Or this” or “Or even this”.](https://papereditor.app/internals-article/effective-range.png)

To get the guaranteed maximum range you need to use a different method.

```objc
NSRange effectiveRange;
id value = [string attribute:NSFontAttributeName
                     atIndex:6
       longestEffectiveRange:&effectiveRange
                     inRange:NSMakeRange(0, string.length)];
```

I suppose, this separation is done to make the _checking_ of the attribute existence faster with the former method as the latter one probably needs to do some range merging to figure out the longest range when multiple ranges overlap. Still — how the `effectiveRange` in the former method is even useful? 🤷🏼‍♂️

The same pair of methods exist to query an `NSDictionary` of all the attributes at a position and the `effectiveRange` for which this unique combination of attributes spans.

```objc
NSRange effectiveRange;
NSDictionary<NSAttributedStringKey, id> attributes =

  [string attributesAtIndex:6
      longestEffectiveRange:&effectiveRange
                    inRange:NSMakeRange(0, string.length)];
```

Finally, there is a convenience method to iterate over attributes within a range. With the longest constant name that ever existed for specifying which mode of attribute range inspection you prefer.

```objc
[string enumerateAttribute:NSFontAttributeName
                   inRange:NSMakeRange(0, string.length)
  options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
  usingBlock:^(id value, NSRange range, BOOL *stop) {
  // do something
}];
```

## Styling

With the foundational knowledge behind us, it’s time to discuss how the syntax highlighting and text styling work in Paper.

As mentioned before, styling means applying special framework-defined attributes to ranges of text. In addition to them, Paper also uses custom attributes to identify the structure of the text before styling it. Here’s the breakdown:

1. **Meta attributes**
  - Defined by the Markdown parser to identify individual parts of the Markdown syntax.
  - These are custom string-value pairs used purely for semantics.
  - They do not influence the visual look of the text.
  - You can think of them as a simplified [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for Markdown.
2. **Styling attributes**
  - The visual attributes applied on top of the parts marked by meta attributes.
  - These are built-in string-value pairs defined by AppKit and UIKit.

![The Mac app with the text “The quick brown fox **jumps** ==over== the ~_lazy_~ dog.” in the center. Meta and styling attributes are labeled.](https://papereditor.app/internals-article/meta-and-styling-attributes.png)

The attributes are kept in sync with:

- The **Markdown text** in `NSTextStorage` that changes due to user input.
- The **text-affecting settings** that change as the user adjusts them from various menu items, sliders, and gestures.

Technically, we can identify three types of events that trigger this attribute update process:

1. **Document opened** — full update of meta attributes and styling attributes.
2. **Text changed** — partial update of meta attributes and styling attributes in the affected part. Most of the time only in the edited text. Sometimes in the whole paragraph. More on that in the next chapter.
3. **Setting changed** — full update of styling attributes but not meta attributes.

![A diagram showing the three events and how much they update the meta and styling attributes.](https://papereditor.app/internals-article/attribute-update-events.png)

In every update there is a well-defined sequence of steps:

1. **Start the text editing transaction**
  - Without a transaction, every attribute change would trigger an expensive layout recalc by the `NSLayoutManager`. Instead, we want to batch all the changes and re-layout only once in step 4.
2. **Parse the Markdown structure**
  - This is where the Markdown string is broken down into pieces denoted by the _meta attributes_.
  - This step is skipped for _setting change_ since the Markdown structure does not change in this case.
3. **Update layout-affecting attributes**
  - The first batch of _styling attributes_.
  - This is every visual attribute that can influence the position or size of the glyphs in the text view.
4. **End the text editing transaction**
5. **Update decorative attributes**
  - The second batch of _styling attributes_.
  - The decorative attributes (or _rendering attributes_ in Apple’s terminology) are applied outside the transaction. The reason is simple — they don’t affect the layout, so updating them is not expensive. And they are not even aware of the transaction since they live in the `NSLayoutManager` itself, not in `NSTextStorage`.

![A diagram showing the five steps of the attribute update process on the text “**jumps** ==over”. During step 3 the font attribute is applied to “jumps”. During step 5 the light gray color is applied to Markdown tags and the background color to “over”.](https://papereditor.app/internals-article/attribute-update-steps.png)

The most important attribute of the layout-affecting ones is `NSParagraphStyle`. It defines the bulk of the values that influence the layout of the lines and paragraphs.

![A diagram breaking down which parts of the text and in which way are affected by the NSParagraphStyle attribute in the Mac app.](https://papereditor.app/internals-article/paragraph-style.png)

The last chunk of attributes that participate in the styling process are the _typing attributes_. They are tied to the attributes at the position preceding the caret (for empty selection) or to the one at the start of the selection (for non-empty selection). Once you type a character, the typing attributes are assigned to the newly inserted text automatically. In a Markdown editor, they are not that important as the styling is derived entirely from the Markdown syntax, but they are crucial for rich text editors where the styles _stick_ to the caret until you turn them off or move the caret to a new location. Despite being a Markdown editor, Paper _does_ have a rich text editing experience called the Preview Mode. In this mode, the editor behaves just like a rich text editor with _toggleable_ typing attributes being highlighted, for example, on the toolbar in the iOS app.

[**DEMO**](https://papereditor.app/internals-article/typing-attributes.mp4)

## Performance

The separation of meta, layout, and decorative attributes plays nicely into keeping certain editor changes fast. For instance, toggling between light and dark modes requires updating only decorative attributes which is very fast as it does not trigger the layout. Setting changes such as text size adjustments, though require a re-layout of the whole document, is still reasonably fast compared to doing that plus a full re-parse of the Markdown structure.

That said, the most crucial performance piece of any text editor is undoubtedly the typing speed. The bad news is that due to how Markdown works, any text change has the potential to affect the styling of the whole paragraph.

[**DEMO**](https://papereditor.app/internals-article/md-affecting-paragraph.mov)

Thus the logical thing to do is to re-parse and re-style the whole paragraph on every keystroke. The problem with that is while this is technically the most correct approach, it can slow down the editing for longer paragraphs. At the same time, if you’re simply typing out a long sentence, the Markdown structure does not change. There is really no need to re-style everything all the time for those simple typing scenarios.

So to make typing snappier, I’ve built an algorithm that looks at the next character being typed as well as what characters are around it. The gist of the logic is that if you’re typing a special Markdown symbol, or the location of the edit is surrounded by one, then you should update the whole paragraph, otherwise you can simply rely on the typing attributes. It’s a simple algorithm that does marvels for the speed of the editor in the majority of typing situations.

![Two diagrams. In the first diagram the letter “p” is inserted into the text “**jums** ==over=” between “m” and “s”. The newly inserted letter “p” is restyled as a result. In the second diagram the letter “*” is deleted from “**jumps** ==over”. The whole paragraph is restyled as a result and “jumps” is no longer bold.](https://papereditor.app/internals-article/typing-optimization.png)

The only nasty exception to the above is when you have code blocks in the document. Code blocks are the only multi-paragraph Markdown constructs in Paper. A keystroke has the potential to re-style the whole document.

[**DEMO**](https://papereditor.app/internals-article/md-code-blocks.mov)

For now, I decided to ignore code blocks in documents beyond a certain character limit. It keeps the editor fast for the majority of users who don’t care about code, at the same time making Paper more useful for dev-adjacent audiences.

The final technique that I use to speed things up is to cache every complex value object in the string-value attribute pair.

- `NSFont`/`UIFont`
- `NSColor`/`UIColor`
- `NSParagraphStyle`

They are being re-assigned on every keystroke and never change unless a text-affecting setting is changed, so it makes sense to reuse them instead of creating new instances every time.

## Meta attributes

Besides the highlighting logic, meta attributes play a crucial role in various features that need to know about the structure of the text.

### Formatting shortcuts

- Toggling styles on a selected piece of Markdown text requires detailed information about the existing Markdown styles inside the selection.
- If the selection completely encloses the same style, then the style is removed.
- If the selection does not contain the same style, then the style is added.
- If the selection partially encloses the same style, then the style is moved to the selection.
- You also need to be careful not to mix the styles that cannot be mixed. The conflicting styles need to be removed first, before a new style can be added. For example, styles that define the type of the paragraph such as _heading_ and _blockquote_ cannot be mixed.

[**DEMO**](https://papereditor.app/internals-article/formatting-shortcuts.mov)

### Jumping between chapters

- Paper has a feature that allows you to jump to the previous or the next edge of the chapter.
- Meta attributes help to locate the headings relative to the position of the caret.

[**DEMO**](https://papereditor.app/internals-article/jump-chapters.mov)

### Outline

- The outline feature relies on being able to traverse every heading.
- Pressing on the item in the outline moves the caret to that chapter.

[**DEMO**](https://papereditor.app/internals-article/outline.mp4)

### Rearranging chapters

- Paper also has a feature that allows rearranging chapters in the outline.

[**DEMO**](https://papereditor.app/internals-article/rearrange-chapters.mov)

### Converting formats

- Converting the Markdown content to RTF, HTML, and DOCX relies on knowing the structure of the text.
- Since Paper [does not include any external libraries](https://papereditor.app/dev#third-party-dependencies), having a pre-parsed model of the text allows me to traverse the structure, building the respective output format in the process.

```objc
- (NSString *)toHtml:(NSMutableAttributedString *)string {
  [self encloseInHtmlTags:string
                         :MdStrongAttributeName
                         :@{
    MdStrong: @[ @"<strong>", @"</strong>" ]
  }];
  [self encloseInHtmlTags:string
                         :MdEmphasisAttributeName
                         :@{
    MdEmphasis: @[ @"<em>", @"</em>" ]
  }];
  [self encloseInHtmlTags:string
                         :MdUnderlineAttributeName
                         :@{
    MdUnderline: @[ @"<u>", @"</u>" ]
  }];
  [self encloseInHtmlTags:string
                         :MdStrikethroughAttributeName
                         :@{
    MdStrikethrough: @[ @"
", @"
" ]
  }];
  [self encloseInHtmlTags:string
                         :MdHighlightAttributeName
                         :@{
    MdHighlight: @[ @"<mark>", @"</mark>" ]
  }];
  [self encloseInHtmlTags:string
                         :MdCodeAttributeName
                         :@{
    MdCode: @[ @"<code>", @"</code>" ]
  }];
  [self encloseInHtmlTags:string
                         :MdHeadingAttributeName
                         :@{
    MdHeading1: @[ @"<h1>", @"</h1>" ],
    MdHeading2: @[ @"<h2>", @"</h2>" ],
    MdHeading3: @[ @"<h3>", @"</h3>" ],
    MdHeading4: @[ @"<h4>", @"</h4>" ],
    MdHeading5: @[ @"<h5>", @"</h5>" ],
    MdHeading6: @[ @"<h6>", @"</h6>" ]
  }];
  [self encloseInHtmlTags:string
                         :ParagraphAttributeName
                         :@{
    Paragraph: @[ @"<p>", @"</p>" ]
  }];

  [self encloseInBlockquoteHtmlTags:string];
  [self encloseInListHtmlTags:string];
  [self transformFootnotesForHtml:string];
  [self deleteCharactersWithAttributes:string :MetaAttributes.tags];
  [self insertHtmlBreaksOnEmptyLines:string];

  return string;
}
```

## Text container math

The most important rule for the text container is to maintain the preferred line length, dividing the remaining space between side insets.

![A diagram breaking down the interface of the Mac app. The text container is centered and the side margins are of the same width.](https://papereditor.app/internals-article/text-container-math.png)

There are however trickier cases where you need to fake the symmetry. Like when the heading tags are placed outside of the regular flow of text. The text container is shifted to the left and the paragraphs are indented with `NSParagraphStyle`.

![A diagram breaking down the interface of the Mac app. Various gaps and dimensions are labeled.](https://papereditor.app/internals-article/text-container-symmetry.png)

While there is enough space, it tries to keep the margins visually symmetrical. If there is no extra space left, then it breaks the symmetry in favor of keeping the specified line length. But only while there is padding remaining on the right side. When there is no padding left, the minimum margins take precedence over keeping the line length to its preferred width.

[**DEMO**](https://papereditor.app/internals-article/text-container-inset.mov)

You can achieve this gradual collapsing with a combination of `min` and `max` functions. It takes a second or two to get your head around the math, but once you do, it feels quite elegant in my opinion. I love this kind of simple mathy code that leads to beautiful visual results.

```objc
- (CGFloat)leftInset {
  return (self.availableInsetWidth - fmin(
    self.availableInsetWidth - self.totalMinInset,
    self.leftPadding
  )) / 2.0;
}

- (CGFloat)rightInset {
  return self.availableInsetWidth - self.leftInset;
}

- (CGFloat)availableInsetWidth {
  return self.availableWidth - self.textContainerWidth;
}

- (CGFloat)textContainerWidth {
  return fmin(
    self.maxContentWidth,
    self.availableWidth - self.totalMinInset
  );
}

- (CGFloat)maxContentWidth {
  return self.lineLength * self.characterWidth + self.leftPadding;
}

- (CGFloat)availableWidth {
  return CGRectGetWidth(self.clipView.bounds);
}

- (CGFloat)totalMinInset {
  return self.minInset * 2.0;
}

- (CGFloat)minInset {
  return
    CGRectGetMinX(self.window.titlebarButtonGroupBoundingRect_) +
    CGRectGetMaxX(self.window.titlebarButtonGroupBoundingRect_);
}

- (CGFloat)leftPadding {
  return [@"### " sizeWithAttributes:@{
    NSFontAttributeName: Font.body
  }].width;
}
```

## Selection anchoring

Text selection always has an anchor point. It’s something we are so used to that we never stop to think about.

On the Mac, we click and drag to select the text and we instinctively know that the selection will increase when dragging to the right and decrease when dragging to the left. But only until we hit the point of the click. Then the opposite happens.

[**DEMO**](https://papereditor.app/internals-article/selection-mac.mov)

On iOS the selection is a bit more interactive. We can drag one edge and then the other one becomes the anchor, and vice versa.

[**DEMO**](https://papereditor.app/internals-article/selection-ios.mp4)

The same logic applies when we extend the selection with the keyboard. Hold the _Option_ key plus a left or a right arrow and you can jump between the edges of the words. Do the same while holding the _Shift_ key, in addition to the _Option_ key, and you can select with word increments. And again — it remembers where you started.

It even works naturally when you first click and drag and then continue extending or shrinking the selection with the keyboard. The initial point of the click remains the anchor.

[**DEMO**](https://papereditor.app/internals-article/selection-anchoring.mov)

## Selection affinity

Another fascinating concept of text editing that you _most probably_ don’t know about is _selection affinity_. Quoting Apple’s documentation:

> Selection affinity determines whether, for instance, the insertion point appears after the last character on a line or before the first character on the following line in cases where text wraps across line boundaries.

My guess is you still have no clue what it means, so let’s see it in action.

Pay attention to the screencast below. When I move the caret with the arrow keys, it simply switches the lines when moving around the wrapping point denoted by the _space_ character. However, if I move the caret to the end of the line with the shortcut, it attaches itself to the right side of the wrapping space while staying on the same line.

[**DEMO**](https://papereditor.app/internals-article/selection-affinity.mov)

There are also other instances where the `TextView` decides to play this trick. It’s a tiny detail and sort of makes sense when you think about it, but quite hard to actually notice.

## Uniform Type Identifiers

The last chapter will focus on cross-app data exchange, but first, we need to discuss the system that underpins it — the UTIs. It’s a hierarchical system where data types _conform to_ (inherit from) parent data types.

- `public.*` types are defined by Apple. They identify the widely accepted formats such as `public.html` and `public.jpeg`.
- Developers can create their own identifiers using the _reverse domain naming scheme_ to avoid collisions.

![A diagram showing the hierarchical structure of UTIs. At the top is “public.data”, below it “public.text”. Then it splits to “public.plain-text” and “public.rtf”. Below “public.plain-text” is “net.daringfireball.markdown”.](https://papereditor.app/internals-article/utis.png)

The benefit of the hierarchical system is that, for example, if your app can view any text format then you don’t need to list all of them — you can just say that it works with `public.text`. And indeed, Paper declares that it can open any text file, and although you won’t get any highlighting, you can still open `.html`, `.rtf`, or any other text format.

![RTF file opened in the Mac app.](https://papereditor.app/internals-article/rtf-in-paper.png)

When exchanging data via a programmatic interface such as the clipboard, UTIs can be used directly. Files however are a bit trickier. File is a cross-platform concept and de-facto identifiers for files in the cross-platform realm are _file extensions_. Even if Apple would redo their systems to rely on some file-level UTI metadata field instead of the file extension (and [it appears they have](https://stackoverflow.com/questions/34927527)), other systems would not know anything about it. So to stay compatible, every UTI can define one or more file extensions that are associated with it.

Now, most of the time you work with either public UTIs or private ones that you’ve created specifically for your app. Things are relatively straightforward in these scenarios. The harder case is when you have a format that’s widely accepted, but not defined by Apple. This is exactly the case with [Markdown](https://daringfireball.net/linked/2011/08/05/markdown-uti). I will explain some of the annoying edge cases with these _semi-public_ UTIs in the next chapter.

## Pasteboard

UTIs transition nicely into the topic of cross-app exchange driven primarily by the _clipboard_, or in Apple’s technical terms — the _pasteboard_.

The pasteboard is nothing more than a dictionary where UTIs are mapped to serialized data — in either textual or binary format. In fact, using the _Clipboard Viewer_ from _Additional Tools for Xcode_ you can inspect the contents of the pasteboard in real time.

[**DEMO**](https://papereditor.app/internals-article/clipboard-viewer.mov)

As you can see, a single _copy_ action writes multiple representations of the same data at once (for backward compatibility some apps also write legacy non-UTI identifiers such as `NeXT Rich Text Format v1.0 pasteboard type`). That’s how, for instance, if you copy from Pages and paste it into [MarkEdit](https://apps.apple.com/app/id1669953820) — you get just the text, but if you paste it into TextEdit — you get the whole shebang.

[**DEMO**](https://papereditor.app/internals-article/paste-to-apps.mov)

As a general rule, editors pick whatever is the _richest_ format they can handle. Some apps provide ways to force a specific format to be used. For example, a common menu item in the _Edit_ menu of rich text editors is _Paste and Match Style_ or _Paste as Plain Text_. It tells the app to use the plain text format from the pasteboard. The styles applied to the pasted text are usually taken from the _typing attributes_.

A fun fact is that drag and drop is also powered by the pasteboard, but a different one. The standard one is called the _general pasteboard_ and it’s used for copy-paste. You can even create custom ones for bespoke cross-app interactions.

Another fun fact is that RTF is basically the serialized form of `NSAttributedString`. Or vice versa, `NSAttributedString` is the programmatic interface for RTF.

```objc
NSAttributedString *string = [NSAttributedString.alloc initWithString:
  @"The quick brown fox jumps over the lazy dog."];
NSData *data = [string dataFromRange:NSMakeRange(0, string.length)
                     documentAttributes:@{
  NSDocumentTypeDocumentOption: NSRTFTextDocumentType
} error:nil];

// {\rtf1\ansi\ansicpg1252\cocoartf2759…
NSLog(@"%@", [NSString.alloc initWithData:data]);
```

This means that `TextView` is out-of-the-box compatible with the pasteboard since it works on top of `NSTextStorage` — the child class of `NSAttributedString`. No extra coding is needed to copy the contents to the pasteboard.

Now, as I mentioned in the last chapter, this is all great for public UTIs. But what about [semi-public](https://daringfireball.net/linked/2011/08/05/markdown-uti) ones like Markdown? From my experience, the cross-app exchange is a mixed bag…

Imagine you want to copy from one Markdown editor and paste it into another one. Let’s say both have implemented the standard protocol to export formats with various levels of richness and to import the richest format given. Copying from the first editor exports Markdown as `public.text` and the rich text representation as `public.rtf`. When pasting to the second editor, it will pick `public.rtf` instead of the native Markdown format since there is no indication that the text is indeed Markdown. You end up with this weird double conversion that leads to all sorts of small formatting issues, such as extra newlines due to slight variations in the way Markdown↔RTF translation works in both apps, as well as just fundamental styling differences between Markdown and RTF. For the user it is obvious — “_I copy Markdown from here and paste it here — it should just copy 1:1_”, but under the hood there is a lot of needless conversion.

For this to work nicely, both apps should magically agree to export the `net.daringfireball.markdown` UTI and prefer it over `public.rtf`. If only one of the apps does it — it won’t make a difference. Paper tried to be a good citizen by exporting the Markdown UTI, but none of the other apps seem to prefer it over rich text. In addition to that, Pages has a weird behavior where it _does_ prefer `net.daringfireball.markdown` over `public.rtf`, but in doing so it just inserts the raw Markdown string as is without converting it to rich text (why-y-y??? 😫). For this reason, I had to drop the Markdown UTI.

“_But why export RTF at all? Markdown is all about plain text — drop RTF and problem solved_” — you might think. Well, that’s true, but I want to provide a seamless copy-paste experience from Paper to rich text editors. And being a good OS citizen, you _should_ provide many formats that represent the copied data, so that the receiving application could pick the richest one it can handle. In Paper, you can copy the Markdown text from the editor and paste it into the Mail app, and it would paste as nicely formatted rich text, not as some variant of Markdown. This is a great experience in my opinion. The only problem is that it often leads to less-than-ideal UX in other cases.

[**DEMO**](https://papereditor.app/internals-article/paper-rich-text.mov)

Another feature closely related to the pasteboard is _sharing_ on iOS. It’s quite similar to copy-paste, only with a bit of UI on top. Your app exports data in various formats and the receiving app decides what format it wants to grab. Strangely enough, UTIs are not used to identify the data (well actually they [kind of are](https://stackoverflow.com/questions/28654593) through some bizarre scripting language in a config file 😱). Rather, classes such as `NSAttributedString`, `NSURL`, and `UIImage` are directly used to represent the type. Unlike the pasteboard that applies to all apps automatically, the sharing feature on iOS requires apps to explicitly opt-in to be present in that top row of apps by providing a _share extension_ with a custom UI.

[**DEMO**](https://papereditor.app/internals-article/sharing.mp4)

## That’s it for now

Check out the [first article](https://papereditor.app/dev) if you haven’t already. It has a lot more tidbits about the app and the development process.