Nerdy internals of an Apple textual content editor
⏳ 19 min learn
On this article, we’ll dive into the small print of the way in which Paper features as a TextView
-based textual content editor for Apple platforms.
The first article was only a warm-up — right here is the place we get to actually geek out! 🤓
Earlier than we begin, I’ll add that in the interim Paper is constructed on the older TextKit 1 framework, so the article is relative to TextKit 1. That stated, all the ideas, abstractions, and ideas mentioned right here nonetheless exist in TextKit 2, both unchanged or below a greater API.
Textual content view
To know how textual content modifying works in native Apple textual content editors, we first want to debate the centerpiece of the entire system — the TextView
class. Technically, NSTextView
and UITextView
have their variations, however the API is analogous sufficient that we are able to deal with them as a single TextView
class. I’ll spotlight the variations the place crucial.
TextView
is an enormous element that solely grows in complexity with every launch of respective working techniques. The TextEdit app consists nearly totally of a single TextView
. When a single class can be utilized to construct a complete app — it’s a beast.
Fortunately, TextView
isn’t just one enormous pile of code. Apple tried to subdivide it right into a bunch of layers — every represented by a flagship class. The layers construct on high of one another to create a textual content modifying expertise.
NSTextStorage
- Shops the uncooked textual content string.
-
Shops the attributes (string-value pairs) assigned to ranges of textual content.
- Types comparable to font and shade (outlined by AppKit and UIKit).
- Any string-value pair that acts as metadata on your wants.
- Emits occasions about textual content and attribute modifications.
NSTextContainer
- Defines the form and dimensions of the realm that hosts textual content symbols (glyphs).
- More often than not it’s a rectangle (duh 🙄) however will be any form.
NSLayoutManager
-
Figures out the scale of the glyphs and the spacings between them by wanting on the ranges of attributes utilized to the textual content string in
NSTextStorage
.- Extracts vector glyphs from the font.
- Converts every textual content character to a number of glyphs. Some symbols and languages want multiple.
- Calculates the scale of every glyph.
- Calculates the distances between glyphs.
- Calculates the distances between strains of glyphs.
-
Lays out every glyph, line by line, into the form outlined by
NSTextContainer
.- Calculates the place each line of textual content begins and ends.
- Calculates what number of strains there are and what’s the complete top of the textual content.
TextView
- Attracts the glyph structure generated by
NSLayoutManager
. - Syncs the peak of the view with the present top of laid-out textual content.
- Manages the textual content choice.
- Manages the caret — empty textual content choice.
- Manages the typing attributes — attributes utilized to the newly inserted textual content.
- Can outline margins (
textContainerInset
) across theNSTextContainer
. - Manages all the extra bells and whistles comparable to dictation, copy-paste, spell verify, and many others.
ScrollView
- Reveals the seen portion of the
TextView
. - Manages scrolling, scroll bars, and zooming.
- Can outline its personal margins (
contentInset
) along with thetextContainerInset
outlined by theTextView
. -
Implementation particulars:
-
AppKit
NSScrollView
comprisesNSClipView
and two situations ofNSScroller
.NSClipView
comprisesNSTextView
.- Thus many separate lessons work collectively to make the scrolling impact.
-
UIKit
UITextView
extends fromUIScrollView
.- Thus
UITextView
holds all the things, together with the scrolling logic. - One other notable element is that shifting the caret exterior the seen space of
UITextView
, bounded bycontentInset
, causesUITextView
to auto-scroll to make sure that the caret stays inside the seen space. You possibly can usually expertise this in iOS textual content editors, the place if the caret strikes behind the keyboard, the editor scrolls to the subsequent line. It is because the undersidecontentInset
is dynamically set to the present top of the keyboard.
-
Attributes
With the final construction of TextView
out of the way in which, let’s zoom in on NSTextStorage
, or relatively its guardian class NSAttributedString
, as it’s the basis of wealthy textual content modifying in Apple’s frameworks.
NSAttributedString
consists of two components:
- A daily textual content string.
- String-value pairs of attributes hooked up to ranges of textual content inside the string.
Attributes are used largely for styling functions, however nothing restricts you from assigning customized string-value pairs on your personal wants.
To get began, let’s make an NSAttributedString
through the API:
NSMutableAttributedString *string = [NSMutableAttributedString.alloc
initWithString:@"The quick brown fox jumps over the lazy dog."];
NSMutableParagraphStyle *fashion = NSMutableParagraphStyle.new;
fashion.firstLineHeadIndent = 30.0;
value:style
range:NSMakeRange(0, string.length)];
[string addAttribute:NSFontAttributeName
value:[NSFont systemFontOfSize:25.0] vary:NSMakeRange(0, string.size)]; [string addAttribute:NSForegroundColorAttributeName
value:NSColor.brownColor
range:NSMakeRange(10, 5)]; [string addAttribute:NSFontAttributeName
value:[NSFont boldSystemFontOfSize:25.0] vary: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] vary:NSMakeRange(35, 4)];
NSRange
is a construction consisting of a location
and a size
. NSMakeRange(10,5)
means a variety of 5
characters ranging from place 10
, or in different phrases, an inclusive vary between positions 10
and 14
. In case totally different ranges outline the identical attribute below the identical place then the final utilized vary takes priority. Within the instance above, the daring and italic fonts overwrite the default font that’s utilized to the entire string.
This code will be simply visualized in TextEdit as it’s just about an NSTextView
with some buttons.
The second huge a part of the API is devoted to checking what attributes are utilized to what ranges. The API itself is kind of peculiar. A whole lot of thought has gone into making it quick and environment friendly, however because of this, the utilization is usually a little bit of a ache.
For example, if you wish to verify whether or not a sure attribute exists at a sure place you’d use this methodology:
id worth = [string attribute:NSFontAttributeName
atIndex:6
effectiveRange:nil];
If the worth
is nil
, then it doesn’t exist. In any other case, it’s the worth of the attribute which on this case is a NSFont
/UIFont
object. So this methodology can be utilized each to question the worth and to verify the existence of the attribute.
Nevertheless it will get higher. You possibly can cross a pointer to the NSRange
construction because the final argument (the nice previous C method to return a number of values from a single operate name):
NSRange effectiveRange;
id worth = [string attribute:NSFontAttributeName
atIndex:6
effectiveRange:&effectiveRange];
And it’ll return both:
- The vary of the continual span of the identical attribute with the identical worth.
- Or the vary of the hole the place the attribute is absent.
Although not precisely… You see the effectiveRange
right here isn’t what you assume it’s. Quoting the documentation:
The vary isn’t essentially the utmost vary lined by the attribute, and its extent is implementation-dependent.
In different phrases, it could possibly be the proper most vary… however it additionally may not be.
“Ahh — I simply love having a little bit of non-determinism in my code!”
To get the assured most vary you must use a special methodology.
NSRange effectiveRange;
id worth = [string attribute:NSFontAttributeName
atIndex:6
longestEffectiveRange:&effectiveRange
inRange:NSMakeRange(0, string.length)];
I suppose, this separation is completed to make the checking of the attribute existence sooner with the previous methodology because the latter one in all probability must do some vary merging to determine the longest vary when a number of ranges overlap. Nonetheless — how the effectiveRange
within the former methodology is even helpful? 🤷🏼♂️
The identical pair of strategies exist to question an NSDictionary
of all of the attributes at a place and the effectiveRange
for which this distinctive mixture of attributes spans.
NSRange effectiveRange;
NSDictionary<NSAttributedStringKey, id> attributes =
[string attributesAtIndex:6
longestEffectiveRange:&effectiveRange
inRange:NSMakeRange(0, string.length)];
Lastly, there’s a comfort methodology to iterate over attributes inside a variety. With the longest fixed identify that ever existed for specifying which mode of attribute vary inspection you favor.
[string enumerateAttribute:NSFontAttributeName
inRange:NSMakeRange(0, string.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(id value, NSRange range, BOOL *stop) {
}];
Styling
With the foundational information behind us, it’s time to debate how the syntax highlighting and textual content styling work in Paper.
As talked about earlier than, styling means making use of particular framework-defined attributes to ranges of textual content. Along with them, Paper additionally makes use of customized attributes to establish the construction of the textual content earlier than styling it. Right here’s the breakdown:
-
Meta attributes
- Outlined by the Markdown parser to establish particular person components of the Markdown syntax.
- These are customized string-value pairs used purely for semantics.
- They don’t affect the visible look of the textual content.
-
Styling attributes
- The visible attributes utilized on high of the components marked by meta attributes.
- These are built-in string-value pairs outlined by AppKit and UIKit.
The attributes are saved in sync with:
- The Markdown textual content in
NSTextStorage
that modifications as a result of person enter. - The text-affecting settings that change because the person adjusts them from numerous menu objects, sliders, and gestures.
Technically, we are able to establish three kinds of occasions that set off this attribute replace course of:
- Doc opened — full replace of meta attributes and styling attributes.
- Textual content modified — partial replace of meta attributes and styling attributes within the affected half. More often than not solely within the edited textual content. Typically in the entire paragraph. Extra on that within the subsequent chapter.
- Setting modified — full replace of styling attributes however not meta attributes.
In each replace there’s a well-defined sequence of steps:
-
Begin the textual content modifying transaction
- With no transaction, each attribute change would set off an costly structure recalc by the
NSLayoutManager
. As an alternative, we wish to batch all of the modifications and re-layout solely as soon as in step [4.].
- With no transaction, each attribute change would set off an costly structure recalc by the
-
Parse the Markdown construction
- That is the place the Markdown string is damaged down into items denoted by the meta attributes.
- This step is skipped for setting change because the Markdown construction doesn’t change on this case.
-
Replace layout-affecting attributes
- The primary batch of styling attributes.
- That is each visible attribute that may affect the place or measurement of the glyphs within the textual content view.
- Finish the textual content modifying transaction
-
Replace ornamental attributes
- The second batch of styling attributes.
- The ornamental attributes (or rendering attributes in Apple’s terminology) are utilized exterior the transaction. The reason being easy — they don’t have an effect on the structure, so updating them isn’t costly. And they aren’t even conscious of the transaction since they reside within the
NSLayoutManager
itself, not inNSTextStorage
.
An important attribute of the layout-affecting ones is NSParagraphStyle
. It defines the majority of the values that affect the structure of the strains and paragraphs.
The final chunk of attributes that take part within the styling course of are the typing attributes. They’re tied to the attributes on the place previous the caret (for empty choice) or to the one at the beginning of the choice (for non-empty choice). When you kind a personality, the typing attributes are assigned to the newly inserted textual content routinely. In a Markdown editor, they aren’t that essential because the styling is derived totally from the Markdown syntax, however they’re essential for wealthy textual content editors the place the types stick to the caret till you flip them off or transfer the caret to a brand new location. Regardless of being a Markdown editor, Paper does have a wealthy textual content modifying expertise known as the Preview Mode. On this mode, the editor behaves identical to a wealthy textual content editor with toggleable typing attributes being highlighted, for instance, on the toolbar within the iOS app.
Efficiency
The separation of meta, structure, and ornamental attributes performs properly into retaining sure editor modifications quick. For example, toggling between mild and darkish modes requires updating solely ornamental attributes which could be very quick because it doesn’t set off the structure. Setting modifications comparable to textual content measurement changes, although require a re-layout of the entire doc, remains to be moderately quick in comparison with doing that plus a full re-parse of the Markdown construction.
That stated, probably the most essential efficiency piece of any textual content editor is undoubtedly the typing pace. The unhealthy information is that as a result of how Markdown works, any textual content change has the potential to have an effect on the styling of the entire paragraph.
Thus the logical factor to do is to re-parse and re-style the entire paragraph on each keystroke. The issue with that’s whereas that is technically probably the most appropriate method, it may possibly decelerate the modifying for longer paragraphs. On the identical time, for those who’re merely typing out an extended sentence, the Markdown construction doesn’t change. There may be actually no must re-style all the things on a regular basis for these easy typing eventualities.
So to make typing snappier, I’ve constructed an algorithm that appears on the subsequent character being typed in addition to what characters are round it. The gist of the logic is that for those who’re typing a particular Markdown image, or the placement of the edit is surrounded by one, then it is best to replace the entire paragraph, in any other case you may merely depend on the typing attributes. It’s a easy algorithm that does marvels for the pace of the editor within the majority of typing conditions.
The one nasty exception to the above is when you’ve got code blocks within the doc. Code blocks are the one multi-paragraph Markdown constructs in Paper. A keystroke has the potential to re-style the entire doc.
For now, I made a decision to disregard code blocks in paperwork past a sure character restrict. It retains the editor quick for almost all of customers who don’t care about code, on the identical time making Paper extra helpful for dev-adjacent audiences.
The ultimate method that I exploit to hurry issues up is to cache each complicated worth object within the string-value attribute pair.
NSFont
/UIFont
NSColor
/UIColor
NSParagraphStyle
They’re being re-assigned on each keystroke and by no means change until a text-affecting setting is modified, so it is smart to reuse them as an alternative of making new situations each time.
Moreover the highlighting logic, meta attributes play a vital position in numerous options that must know in regards to the construction of the textual content.
Formatting shortcuts
- Toggling types on a particular piece of Markdown textual content requires detailed details about the prevailing Markdown types inside the choice.
- If the choice fully encloses the identical fashion, then the fashion is eliminated.
- If the choice doesn’t include the identical fashion, then the fashion is added.
- If the choice partially encloses the identical fashion, then the fashion is moved to the choice.
- You additionally should be cautious to not combine the types that can’t be blended. The conflicting types should be eliminated first, earlier than a brand new fashion will be added. For instance, types that outline the kind of the paragraph comparable to heading and blockquote can’t be blended.
Leaping between chapters
- Paper has a characteristic that means that you can leap to the earlier or the subsequent fringe of the chapter.
- Meta attributes assist to find the headings relative to the place of the caret.
Define
- The define characteristic depends on with the ability to traverse each heading.
- Urgent on the merchandise within the define strikes the caret to that chapter.
Rearranging chapters
- Paper additionally has a characteristic that permits rearranging chapters within the define.
Changing codecs
- Changing the Markdown content material to RTF, HTML, and DOCX depends on realizing the construction of the textual content.
- Since Paper does not include any external libraries, having a pre-parsed mannequin of the textual content permits me to traverse the construction, constructing the respective output format within the course of.
- (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: @[ @"<s>", @"</s>" ]
}];
[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;
}
Textual content container math
An important rule for the textual content container is to take care of the popular line size, dividing the remaining house between aspect insets.
There are nonetheless trickier circumstances the place you must faux the symmetry. Like when the heading tags are positioned exterior of the common circulation of textual content. The textual content container is shifted to the left and the paragraphs are indented with NSParagraphStyle
.
Whereas there may be sufficient house, it tries to maintain the margins visually symmetrical. If there is no such thing as a additional house left, then it breaks the symmetry in favor of retaining the desired line size. However solely whereas there may be padding remaining on the best aspect. When there is no such thing as a padding left, the minimal margins take priority over retaining the road size to its most popular width.
You possibly can obtain this gradual collapsing with a mix of min
and max
features. It takes a second or two to get your head across the math, however when you do, it feels fairly elegant for my part. I like this sort of easy mathy code that results in stunning visible outcomes.
- (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;
}
Choice anchoring
Textual content choice at all times has an anchor level. It’s one thing we’re so used to that we by no means cease to consider.
On the Mac, we click on and drag to pick the textual content and we instinctively know that the choice will enhance when dragging to the best and reduce when dragging to the left. However solely till we hit the purpose of the press. Then the other occurs.
On iOS the choice is a little more interactive. We will drag one edge after which the opposite one turns into the anchor, and vice versa.
The identical logic applies once we lengthen the choice with the keyboard. Maintain the Possibility key plus a left or a proper arrow and you’ll leap between the sides of the phrases. Do the identical whereas holding the Shift key, along with the Possibility key, and you’ll choose with phrase increments. And once more — it remembers the place you began.
It even works naturally once you first click on and drag after which proceed extending or shrinking the choice with the keyboard. The preliminary level of the press stays the anchor.
Choice affinity
One other fascinating idea of textual content modifying that you just most likely don’t find out about is choice affinity. Quoting Apple’s documentation:
Choice affinity determines whether or not, for example, the insertion level seems after the final character on a line or earlier than the primary character on the next line in circumstances the place textual content wraps throughout line boundaries.
My guess is you continue to haven’t any clue what it means, so let’s see it in motion.
Take note of the screencast under. After I transfer the caret with the arrow keys, it merely switches the strains when shifting across the wrapping level denoted by the house character. Nonetheless, if I transfer the caret to the tip of the road with the shortcut, it attaches itself to the best aspect of the wrapping house whereas staying on the identical line.
There are additionally different situations the place the TextView
decides to play this trick. It’s a tiny element and form of is smart when you consider it, however fairly arduous to really discover.
Uniform Kind Identifiers
The final chapter will deal with cross-app information change, however first, we have to talk about the system that underpins it — the UTIs. It’s a hierarchical system the place information varieties conform to (inherit from) guardian information varieties.
public.*
varieties are outlined by Apple. They establish the broadly accepted codecs comparable topublic.html
andpublic.jpeg
.- Builders can create their very own identifiers utilizing the reverse area naming scheme to keep away from collisions.
The good thing about the hierarchical system is that, for instance, in case your app can view any textual content format then you definitely don’t must record all of them — you may simply say that it really works with public.textual content
. And certainly, Paper declares that it may possibly open any textual content file, and though you received’t get any highlighting, you may nonetheless open .html
, .rtf
, or some other textual content format.
When exchanging information through a programmatic interface such because the clipboard, UTIs can be utilized immediately. Information nonetheless are a bit trickier. File is a cross-platform idea and de-facto identifiers for recordsdata within the cross-platform realm are file extensions. Even when Apple would redo their techniques to depend on some file-level UTI metadata area as an alternative of the file extension (and it appears they have), different techniques wouldn’t know something about it. So to remain suitable, each UTI can outline a number of file extensions which are related to it.
Now, more often than not you’re employed with both public UTIs or personal ones that you just’ve created particularly on your app. Issues are comparatively easy in these eventualities. The more durable case is when you’ve got a format that’s broadly accepted, however not outlined by Apple. That is precisely the case with Markdown. I’ll clarify among the annoying edge circumstances with these semi-public UTIs within the subsequent chapter.
Pasteboard
UTIs transition properly into the subject of cross-app change pushed primarily by the clipboard, or in Apple’s technical phrases — the pasteboard.
The pasteboard is nothing greater than a dictionary the place UTIs are mapped to serialized information — in both textual or binary format. In truth, utilizing the Clipboard Viewer from Extra Instruments for Xcode you may examine the contents of the pasteboard in actual time.
As you may see, a single copy motion writes a number of representations of the identical information directly (for backward compatibility some apps additionally write legacy non-UTI identifiers comparable to NeXT Wealthy Textual content Format v1.0 pasteboard kind
). That’s how, for example, for those who copy from Pages and paste it into MarkEdit — you get simply the textual content, however for those who paste it into TextEdit — you get the entire shebang.
As a common rule, editors decide no matter is the richest format they will deal with. Some apps present methods to pressure a selected format for use. For instance, a standard menu merchandise within the Edit menu of wealthy textual content editors is Paste and Match Model or Paste as Plain Textual content. It tells the app to make use of the plain textual content format from the pasteboard. The types utilized to the pasted textual content are often taken from the typing attributes.
A enjoyable truth is that drag and drop can also be powered by the pasteboard, however a special one. The usual one is named the common pasteboard and it’s used for copy-paste. You possibly can even create customized ones for bespoke cross-app interactions.
One other enjoyable truth is that RTF is principally the serialized type of NSAttributedString
. Or vice versa, NSAttributedString
is the programmatic interface for RTF.
NSAttributedString *string = [NSAttributedString.alloc initWithString:
@"The quick brown fox jumps over the lazy dog."];
NSData *information = [string dataFromRange:NSMakeRange(0, string.length)
documentAttributes:@{
NSDocumentTypeDocumentOption: NSRTFTextDocumentType
} error:nil];
NSLog(@"%@", [NSString.alloc initWithData:data]);
Because of this TextView
is out-of-the-box suitable with the pasteboard since it really works on high of NSTextStorage
— the kid class of NSAttributedString
. No additional coding is required to repeat the contents to the pasteboard.
Now, as I discussed within the final chapter, that is all nice for public UTIs. However what about semi-public ones like Markdown? From my expertise, the cross-app change is a blended bag…
Think about you wish to copy from one Markdown editor and paste it into one other one. Let’s say each have carried out the usual protocol to export codecs with numerous ranges of richness and to import the richest format given. Copying from the primary editor exports Markdown as public.textual content
and the wealthy textual content illustration as public.rtf
. When pasting to the second editor, it should decide public.rtf
as an alternative of the native Markdown format since there is no such thing as a indication that the textual content is certainly Markdown. You find yourself with this bizarre double conversion that results in all types of small formatting points, comparable to additional newlines as a result of slight variations in the way in which Markdown↔RTF translation works in each apps, in addition to simply basic styling variations between Markdown and RTF. For the person it’s apparent — “I copy Markdown from right here and paste it right here — it ought to simply copy 1:1”, however below the hood there may be plenty of useless conversion.
For this to work properly, each apps ought to magically comply with export the web.daringfireball.markdown
UTI and like it over public.rtf
. If solely one of many apps does it — it received’t make a distinction. Paper tried to be citizen by exporting the Markdown UTI, however not one of the different apps appear to want it over wealthy textual content. Along with that, Pages has a bizarre conduct the place it does want web.daringfireball.markdown
over public.rtf
, however in doing so it simply inserts the uncooked Markdown string as is with out changing it to wealthy textual content (why-y-y??? 😫). Because of this, I needed to drop the Markdown UTI.
“However why export RTF in any respect? Markdown is all about plain textual content — drop RTF and downside solved” — you would possibly assume. Effectively, that’s true, however I wish to present a seamless copy-paste expertise from Paper to wealthy textual content editors. And being OS citizen, you ought to present many codecs that characterize the copied information, in order that the receiving software may decide the richest one it may possibly deal with. In Paper, you may copy the Markdown textual content from the editor and paste it into the Mail app, and it might paste as properly formatted wealthy textual content, not as some variant of Markdown. It is a nice expertise for my part. The one downside is that it usually results in less-than-ideal UX in different circumstances.
One other characteristic intently associated to the pasteboard is sharing on iOS. It’s fairly much like copy-paste, solely with a little bit of UI on high. Your app exports information in numerous codecs and the receiving app decides what format it desires to seize. Surprisingly sufficient, UTIs usually are not used to establish the information (effectively truly they kind of are via some weird scripting language in a config file 😱). Fairly, lessons comparable to NSAttributedString
, NSURL
, and UIImage
are immediately used to characterize the kind. Not like the pasteboard that applies to all apps routinely, the sharing characteristic on iOS requires apps to explicitly opt-in to be current in that high row of apps by offering a share extension with a customized UI.
That’s it for now
Try the first article for those who haven’t already. It has much more tidbits in regards to the app and the event course of.
PS — I like sweating the small print. In the event you assume I may be helpful to you → reach out. 😉