How to draw a custom attribute in
The Cocoa Text System is incredibly flexible; but not nearly as well-documented as it should be given its power. The classes and methods themselves are completely documented as is the “big picture” - but there’s a lot of intermediate documentation that’s missing.
In this tutorial, we’ll build an app that draws a custom attribute in an
NSTextView like this:
NSAttributedString is great for drawing standard attributes such as font, font size, foreground and background colors; but it gets more complicated when you need to some something that requires actual drawing. This tutorial will show you how to do simple drawing of a custom attribute.
Download the example project from Github. You need Xcode 4.5 for this project; so if you don’t have it - go update Xcode first.
NSAttributedString for decorated text
NSAttributedString and its mutable counterpart
NSMutableAttributedString are used to draw decorated text. Using these classes, you can create strings with attributes that describe how the string should look when drawn. For example, you can add font and color attributes like this:
1 2 3 4
This creates a string whose font size attribute is 24.0 pt. And if we want to display the
attributedString in an
With the mutable variant
NSMutableAttributedString you can add and remove attributes dynamically:
1 2 3
which will render like this:
Of course, you can also combine attributes:
1 2 3 4 5 6 7 8 9
NSMutableAttributedString tracks changes to its string
If you want to change the underlying
NSMutableAttributedString without disturbing its attributes, you can use its
mutableString method to obtain an
NSMutableString that you can manipulate behind its back, while the
NSMutableAttributedString tracks the changes. In fact the object you get back from
mutableString is not actually an instance of
NSMutableString but an instance of
NSMutableStringProxyForMutableAttributedString instead. This proxy object is responsible for the tracking behavior internally.
What about custom attributes, then?
Let’s get started building the custom attribute. The drawing is done in the context of a layout manager - a subclass of
NSLayoutManager Since our intent is to use our custom attribute in the context of an
NSTextView we should look at the architecture of that class first.
NSTextView has a single text container in which is lays out text. The
NSTextContainer is a rectangular region in which to layout text. Each
NSTextView has a default text container, but it is possible to replace the text container using the
replaceTextContainer method. The text container uses a layout manager to layout and draw the text. There is readonly access to the text container’s layout manager on
NSTextView. In order to give
NSTextView a new layout manager, we have to set it on a new
So let’s start with a custom text view that we’ll call
CCFTextView. You can find the source code in the “view” folder. This text view basically does on thing - replace its
1 2 3 4 5 6 7 8 9 10 11 12 13 14
commonInit function is called from either
initWithFrame: so that no matter how the
CCFTextView gets initialized, we replace its text container’s layout manager with our own subclass. Let’s look at the
NSLayoutManager subclass -
CCFCustomLayoutManager in the “helpers+managers” directory. In the header file “CCFCustomLayouManager.h” we define a few constants.
CCFSpecialHighlighterAttributeName is the name of our custom attribute and
CCFLineColorKey are keys to the dictionary value of our attribute.
In the implementation of our layout manager, we override a single method
drawGlyphsForGlyphRange:atPoint:. Here we’ll digress about glyphs vs. characters.
Glyphs versus characters
The character can is the smallest unit of a written language that has meaning. In Roman and other alphabets, it maps to a particular sound in the spoken counterpart of the written language. However in the case of other languages, like Chinese, it can represent an entire word.
A glyph on the other hand is a graphically-concrete form of a character.
The distinction is important, because while we’re manipulating characters in our code, the text system is working behind the scenes laying out glyphs, not characters. In this case, we need to do both. That’s why out
NSLayoutManager subclass overrides
drawGlyphsForGlyphRange:atPoint. So let’s look a little more closely at what we do in this method, which we’ll build up from pseudo-code
1 2 3 4 5 6 7 8 9 10
First, since we need to refer to the character sequence when we do the mapping, we need a source for that mapping. Fortunately,
NSLayoutManager keeps a reference to its
NSTextStorage object. This object is a subclass of
NSMutableAttributedString. We will get this reference and copy
glyphsToShow to a local variable so that we can iterate over its span.
1 2 3 4 5 6 7 8 9 10 11 12 13
Now, we take care of the glyph-to-character mapping:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Then to check if the attribute is set on this
charRange, we just test for non-nil:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Finally, the drawing is the easiest part. We just need to bracket our drawing code with calls to save then restore the
NSGraphicsContext before drawing. To get the rectangle in which our glyph is drawn, we ask for the
boundingRectForGlyphRange:inTextContainer:. Lastly, we have our completed implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
Let’s turn our attention to
CCFMainWindowController where our attributes are being managed. When the user presses the highlight button, we want to tell the text view to apply our attribute to the selection - which is what we do in
1 2 3 4 5 6 7 8 9 10 11 12 13
The rest of the code in
CCFMainWindowController is for setup and for observing for changes in the highlight and line colors. Using Key-value observing, we are able to detect when the colors change and re-do our markup accordingly.
Athough here’s much more to the text system in Cocoa you should have a good starting point for custom attributes.
Question? Comments? Tweet Alan