Chapter 10. Text

Drawing text into your app’s interface is one of the most complex and powerful things that iOS does for you. Fortunately, iOS also shields you from as much of that complexity as you like. All you need is some text to draw, and possibly an interface object to draw it for you.

Text to appear in your app’s interface will be an NSString or an NSAttributedString. NSAttributedString adds text styling to an NSString, including runs of different character styles, along with paragraph-level features such as alignment, line spacing, and margins.

To make your NSString or NSAttributedString appear in the interface, you can draw it into a graphics context, or hand it to an interface object that knows how to draw it:

Self-drawing text
Both NSString and NSAttributedString have methods (supplied by the NSStringDrawing category) for drawing themselves into any graphics context.
Text-drawing interface objects

Interface objects that know how to draw an NSString or NSAttributedString are:

(Another way of drawing text is to use a UIWebView, a scrollable view displaying rendered HTML. UIWebView can also display various additional document types, such as PDF, RTF, and .doc. UIWebViews are a somewhat different technology, and are discussed in Chapter 11.)

Deep under the hood, all text drawing is performed through a low-level technology with a C API called Core Text. Before iOS 7, certain powerful and useful text-drawing features were available only by working with Core Text.

New in iOS 7, however, is Text Kit, a middle-level technology with an Objective-C API, lying on top of Core Text. UITextView in iOS 7 is largely just a lightweight drawing wrapper around Text Kit, and Text Kit can also draw directly into a graphics context. By working with Text Kit, you can readily do all sorts of useful text-drawing tricks that previously would have required you to sweat your way through Core Text.

There are two ways of describing a font: as a UIFont (suitable for use in an NSString or a UIKit interface object) or as a CTFont (suitable for Core Text). Using CTFont, it is fairly easy to perform various convenient font transformations, such as deriving one font from another through the addition of a symbolic trait; for example, you could say, “Here’s a font; please give me a bold version of it if there is one.”

Before iOS 7, CTFont and UIFont were unfortunately not toll-free bridged to one another, and what you usually started with and wanted to end with was a UIFont; thus, in order to perform font transformations, it was necessary to convert a UIFont to a CTFont manually, work with the CTFont, and then convert back to a UIFont manually — which was by no means trivial.

In iOS 7, however, UIFont and CTFont are toll-free bridged to one another. Moreover, another important Core Text type, CTFontDescriptorRef, is toll-free bridged to a new iOS 7 class, UIFontDescriptor, which can be helpful for performing font transformations.

A font (UIFont, toll-free bridged in iOS 7 to Core Text’s CTFontRef) is an extremely simple object. You specify a font by its name and size by calling the UIFont class method fontWithName:size:, and you can also transform a font of one size to the same font in a different size. UIFont also provides some methods for learning a font’s various measurements, such as its lineHeight and capHeight.

In order to ask for a font, you have to know the font’s name. Every font variant (bold, italic, and so on) counts as a different font, and font variants are clumped into families. To learn, in the console, the name of every installed font, you would say:

for (NSString* s in [UIFont familyNames])
    NSLog(@"%@: %@", s, [UIFont fontNamesForFamilyName:s]);

You can specify a font by its family name or by its font name (technically, its PostScript name). For example, @"Avenir" is a family name; the plain font within that family is @"Avenir-Roman". Either is legal as the first argument of fontWithName:size:.

A few fonts can be obtained with reference to their functionality; for example, you can ask for systemFontOfSize: to get the font used by default in a UIButton. You should never use the name of such a font for anything, however, as the details are private and subject to change.

New in iOS 7 is a set of fonts that you specify by their intended usage rather than by name. These are the so-called Dynamic Type fonts. They are linked to the slider that the user can adjust in the Settings app, under General → Text Size. The idea is that if you have text for the user to read or edit (as opposed, say, to the static text of a button), you can use a Dynamic Type font; it will be sized and styled for you in accordance with the user’s Text Size preference and the role that this text is to play in your layout.

To obtain a Dynamic Type font, call preferredFontForTextStyle:. Possible roles that you can supply as the argument are:

You’ll probably want to experiment with specifying various roles for your individual pieces of text, to see which looks appropriate in context. For example, in Figure 6-1, the headlines are UIFontTextStyleSubheadline and the blurbs are UIFontTextStyleCaption1.

Dynamic Type fonts are not actually dynamic; preferredFontForTextStyle: will return a font whose size is proportional to the user’s Text Size preference only at the moment when it is called. If the user changes that preference, you’ll need to call preferredFontForTextStyle: again. To hear about such changes, register for UIContentSizeCategoryDidChangeNotification. When the notification arrives, you will need to set the fonts for your Dynamic Type–savvy text all over again. This, in turn, may have consequences for the physical features of your interface as a whole; autolayout can be a big help here (Chapter 1).

In this example, we have a label (self.lab) whose font uses Dynamic Type; we set its font both when the label first appears in the interface and subsequently whenever UIContentSizeCategoryDidChangeNotification arrives:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self doDynamicType:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(doDynamicType:)
        name:UIContentSizeCategoryDidChangeNotification
        object:nil];
}
- (void) doDynamicType: (NSNotification*) n {
    self.lab.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
}

In the nib editor, wherever the Attributes inspector lets you supply a font for an interface object, the Dynamic Type roles are available in a pop-up menu. But you will still have to set the font of every such interface object again, in code, when UIContentSizeCategoryDidChangeNotification arrives.

You are not limited to fonts installed by default as part of iOS 7. There are two other ways to obtain additional fonts:

Include a font in your app bundle
A font included at the top level of your app bundle will be loaded at launch time if your Info.plist lists it under the “Fonts provided by application” key (UIAppFonts).
Download a font in real time
All OS X fonts are available for download from Apple’s servers; you can obtain and install one while your app is running.

To download a font in real time, you’ll have specify the font as a font descriptor (discussed in the next section) and drop down to the level of Core Text (@import CoreText) to call CTFontDescriptorMatchFontDescriptorsWithProgressHandler. This function takes a block which is called repeatedly at every stage of the download process; the block is called on a background thread, so if you want to use the downloaded font immediately in the interface, you must step out to the main thread (see Chapter 25).

In this example, I’ll attempt to use Lucida Grande as my UILabel’s font; if it isn’t installed, I’ll attempt to download it and then use it as my UILabel’s font. I’ve inserted a lot of unnecessary logging to mark the stages of the download process:

NSString* name = @"LucidaGrande";
CGFloat size = 12;
UIFont* f = [UIFont fontWithName:name size:size];
if (f) {
    self.lab.font = f;
    NSLog(@"%@", @"already installed");
    return;
}
NSLog(@"%@", @"attempting to download font");
UIFontDescriptor* desc =
    [UIFontDescriptor fontDescriptorWithName:name size:size];
CTFontDescriptorMatchFontDescriptorsWithProgressHandler(
    (__bridge CFArrayRef)@[desc], nil,
    ^(CTFontDescriptorMatchingState state, CFDictionaryRef prog) {
     if (state == kCTFontDescriptorMatchingDidBegin) {
         NSLog(@"%@", @"matching did begin");
     }
     else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
         NSLog(@"%@", @"downloading will begin");
     }
     else if (state == kCTFontDescriptorMatchingDownloading) {
         NSDictionary* d = (__bridge NSDictionary*)prog;
         NSLog(@"progress: %@%%",
             d[(__bridge NSString*)kCTFontDescriptorMatchingPercentage]);
     }
     else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {
         NSLog(@"%@", @"downloading did finish");
     }
     else if (state == kCTFontDescriptorMatchingDidFailWithError) {
         NSLog(@"%@", @"downloading failed");
     }
     else if (state == kCTFontDescriptorMatchingDidFinish) {
         NSLog(@"%@", @"matching did finish");
         dispatch_async(dispatch_get_main_queue(), ^{
             UIFont* f = [UIFont fontWithName:name size:size];
             if (f) {
                 NSLog(@"%@", @"got the font!");
                 self.lab.font = f;
             }
         });
     }
     return (bool)YES;
 });

A font descriptor (UIFontDescriptor, new in iOS 7, toll-free bridged to Core Text’s CTFontDescriptorRef) is a way of describing a font, or converting between one font description and another, in terms of its features. For example, given a font descriptor, you can ask for a corresponding italic font descriptor like this:

desc = [desc fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic];

If desc was originally a descriptor for Avenir 15, it is now a descriptor for Avenir-Oblique 15. However, it is not the font Avenir-Oblique 15; a font descriptor is not a font.

To convert from a font to a font descriptor, take its fontDescriptor property; to convert from a font descriptor to a font, call the UIFont class method fontWithDescriptor:size:, typically supplying a size of 0 to signify that the size should not change. Thus, this will be a typical pattern in your code, as you convert from font to font descriptor to perform some transformation, and then back to font:

UIFont* font = // ...
UIFontDescriptor* desc = [font fontDescriptor];
desc = // font descriptor derived from desc
font = [UIFont fontWithDescriptor:desc size:0];

This same technique is useful also for obtaining styled variants of the Dynamic Type fonts. A UIFontDescriptor class method, preferredFontDescriptorWithTextStyle:, saves you from having to start with a UIFont. In this example, I form an NSAttributedString whose font is mostly UIFontTextStyleBody, but with one italicized word (Figure 10-1):

UIFontDescriptor* body =
    [UIFontDescriptor preferredFontDescriptorWithTextStyle:
        UIFontTextStyleBody];
UIFontDescriptor* emphasis =
    [body fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic];
UIFont* fbody = [UIFont fontWithDescriptor:body size:0];
UIFont* femphasis = [UIFont fontWithDescriptor:emphasis size:0];
NSString* s = @"This is very important!";
NSMutableAttributedString* mas =
    [[NSMutableAttributedString alloc] initWithString:s
     attributes:@{NSFontAttributeName:fbody}];
[mas addAttribute:NSFontAttributeName value:femphasis
    range:[s rangeOfString:@"very"]];

Unfortunately, converting between fonts by calling fontDescriptorWithSymbolicTraits: doesn’t work for every font family. (I regard this as a bug.) You might have to drop down to the level of Core Text. Fortunately, in iOS 7, CTFontRef and UIFont are toll-free bridged, so that’s not such a daunting prospect. You’ll have to @import CoreText first:

UIFont* f = [UIFont fontWithName:@"GillSans" size:15];
CTFontRef font2 =
    CTFontCreateCopyWithSymbolicTraits (
        (__bridge CTFontRef)f, 0, nil,
         kCTFontItalicTrait, kCTFontItalicTrait);
UIFont* f2 = CFBridgingRelease(font2);

CTFontCreateCopyWithSymbolicTraits takes two bitmasks: the first lists the traits you care about, and the second says which traits those are. For example, suppose I’m starting with a font that might or might not be bold, and I want to obtain its italic variant — meaning that if it is bold, I want a bold italic font. It isn’t enough to supply a bitmask whose value is kCTFontItalicTrait, because this appears to switch italics on and everything else off. Thus, the second bitmask says, “Only this one bit is important to me.” By the same token, to get a nonitalic variant of a font that might be italic, you’d supply 0 as the fourth argument and kCTFontItalicTrait as the fifth argument.

Another use of font descriptors is to access hidden built-in typographical features of individual fonts. In this example, I’ll obtain a variant of the Didot font that draws its minuscules as small caps (Figure 10-2). Before iOS 7, you could obtain the small caps Didot font at the level of Core Text, but you couldn’t use it in a UIKit interface object, because it wasn’t a UIFont. In iOS 7, everything happens at the UIKit level (though you will need to @import CoreText to get the symbolic feature names kLetterCaseType and kSmallCapsSelector; alternatively, use @3 for each):

UIFontDescriptor* desc =
    [UIFontDescriptor fontDescriptorWithName:@"Didot" size:18];
NSArray* arr =
    @[@{UIFontFeatureTypeIdentifierKey:@(kLetterCaseType),
        UIFontFeatureSelectorIdentifierKey:@(kSmallCapsSelector)}];
desc =
    [desc fontDescriptorByAddingAttributes:
        @{UIFontDescriptorFeatureSettingsAttribute:arr}];
UIFont* f = [UIFont fontWithDescriptor:desc size:0];

The basis of styled text — that is, text consisting of multiple style runs, with different font, size, color, and other text features in different parts of the text — is the attributed string. Attributed strings (NSAttributedString and its mutable subclass, NSMutableAttributedString) have been around in iOS for a long time, but before iOS 6 they were difficult to use — you had to drop down to the level of Core Text — and they couldn’t be used at all in connection with UIKit interface classes such as UILabel and UITextView. Thus, such interface classes couldn’t display styled text. In iOS 6, NSAttributedString became a first-class citizen; it can now be used to draw styled text directly, and can be drawn by built-in interface classes.

An NSAttributedString consists of an NSString (its string) plus the attributes, applied in ranges. For example, if the string “one red word” is blue except for the word “red” which is red, and if these are the only changes over the course of the string, then there are three distinct style runs — everything before the word “red,” the word “red” itself, and everything after the word “red.” However, we can apply the attributes in two steps, first making the whole string blue, and then making the word “red” red, just as you would expect.

The attributes applied to a range of an attributed string are described in dictionaries. Each possible attribute has a predefined name, used as a key in these dictionaries; here are some of the most important attributes (for the full list, see Apple’s NSAttributedString UIKit Additions Reference):

NSKernAttributeName
An NSNumber wrapping the floating-point amount of kerning. A negative value brings a glyph closer to the following glyph; a positive value adds space between them. (In iOS 6, the special value [NSNull null] turns on inherent autokerning if the font supports it; in iOS 7, autokerning is the default.)
NSStrikethroughStyleAttributeName
NSUnderlineStyleAttributeName

Prior to iOS 7, could be only an NSNumber wrapping 0 or 1. Now, an NSNumber wrapping one of these values describing the line weight (despite Underline in their names, they apply equally to strikethrough):

Optionally, you may append (using logical-or) a specification of the line pattern, with names like NSUnderlinePatternDot, NSUnderlinePatternDash, and so on.

Optionally, you may append (using logical-or) NSUnderlineByWord; if you do not, then if the underline or strikethrough range involves multiple words, the whitespace between the words will be underlined or struck through.

NSStrikethroughColorAttributeName
NSUnderlineColorAttributeName
A UIColor. New in iOS 7. If not defined, the foreground color is used.
NSStrokeWidthAttributeName
An NSNumber wrapping a float. The stroke width is peculiarly coded. If it isn’t zero, it’s either a positive or negative float (wrapped in an NSNumber). If it’s positive, then the text glyphs are stroked but not filled, giving an outline effect, and the foreground color is used unless a separate stroke color is defined. If it’s negative, then its absolute value is the width of the stroke, and the glyphs are both filled (with the foreground color) and stroked (with the stroke color).
NSStrokeColorAttributeName
The stroke color, a UIColor.
NSShadowAttributeName
An NSShadow object. An NSShadow is just a glorified struct (what Apple calls a “value object”), combining a shadowOffset, shadowColor, and shadowBlurRadius.
NSTextEffectAttributeName
If defined, the only possible value is NSTextEffectLetterpressStyle. New in iOS 7.
NSAttachmentAttributeName
An NSTextAttachment object. New in iOS 7. A text attachment is basically an inline image. I’ll discuss text attachments later on.
NSLinkAttributeName
An NSURL. New in IOS 7. In a noneditable, selectable UITextView, the link is tappable to go to the URL (depending on your implementation of the UITextViewDelegate method textView:shouldInteractWithURL:inRange:). By default, appears as blue without an underline in a UITextView. Appears as blue with an underline in a UILabel, but is not a tappable link there.
NSBaselineOffsetAttributeName
NSObliquenessAttributeName
NSExpansionAttributeName
An NSNumber wrapping a float. New in iOS 7.
NSParagraphStyleAttributeName

An NSParagraphStyle object. This is basically just a glorified struct, assembling text features that apply properly to paragraphs as a whole, not merely to characters, even if your string consists only of a single paragraph. Here are its most important properties:

To construct an NSAttributedString, you can call initWithString:attributes: if the entire string has the same attributes; otherwise, you’ll use its mutable subclass NSMutableAttributedString, which lets you set attributes over a range.

To construct an NSParagraphStyle, you’ll use its mutable subclass NSMutableParagraphStyle. (The properties of NSParagraphStyle itself are all read-only, for historical reasons.) It is sufficient to apply a paragraph style to the first character of a paragraph; to put it another way, the paragraph style of the first character of a paragraph dictates how the whole paragraph is rendered.

Both NSAttributedString and NSParagraphStyle come with default values for all attributes, so you only have to set the attributes you care about.

We now know enough for an example! I’ll draw my attributed strings in a disabled (noninteractive) UITextView; its background is white, but its superview’s background is gray, so you can see the text view’s bounds relative to the text. (Ignore the text’s vertical positioning, which is configured by applying a top contentInset.)

First, two words of my attributed string are made extra-bold by stroking in a different color. I start by dictating the entire string and the overall style of the text; then I apply the special style to the two stroked words (Figure 10-3):

NSString* s1 = @"The Gettysburg Address, as delivered on a certain occasion "
    @"(namely Thursday, November 19, 1863) by A. Lincoln";
NSMutableAttributedString* content =
    [[NSMutableAttributedString alloc]
     initWithString:s1
     attributes:
         @{
           NSFontAttributeName:
               [UIFont fontWithName:@"Arial-BoldMT" size:15],
           NSForegroundColorAttributeName:
               [UIColor colorWithRed:0.251 green:0.000 blue:0.502 alpha:1]
         }];
NSRange r = [s1 rangeOfString:@"Gettysburg Address"];
[content addAttributes:
    @{
      NSStrokeColorAttributeName:[UIColor redColor],
      NSStrokeWidthAttributeName: @-2.0
     } range:r];
self.tv.attributedText = content;

Carrying on from the previous example, I’ll also make the whole paragraph centered and indented from the edges of the text view. To do so, I create the paragraph style and apply it to the first character. Note how the margins are dictated: the tailIndent is negative, to bring the right margin leftward, and the firstLineHeadIndent must be set separately, as the headIndent does not automatically apply to the first line (Figure 10-4):

NSMutableParagraphStyle* para = [NSMutableParagraphStyle new];
para.headIndent = 10;
para.firstLineHeadIndent = 10;
para.tailIndent = -10;
para.lineBreakMode = NSLineBreakByWordWrapping;
para.alignment = NSTextAlignmentCenter;
para.paragraphSpacing = 15;
[content addAttribute:NSParagraphStyleAttributeName
                value:para range:NSMakeRange(0,1)];
self.tv.attributedText = content;

In this next example, I’ll enlarge the first character of a paragraph. I assign the first character a larger font size, I expand its width slightly (a new iOS 7 feature), and I reduce its kerning (Figure 10-5):

NSString* s2 = @"Fourscore and seven years ago, our fathers brought forth "
@"upon this continent a new nation, conceived in liberty and dedicated "
@"to the proposition that all men are created equal.";
NSMutableAttributedString* content2 =
[[NSMutableAttributedString alloc]
 initWithString:s2
 attributes:
 @{
   NSFontAttributeName:
       [UIFont fontWithName:@"HoeflerText-Black" size:16]
   }];
[content2 addAttributes:
 @{
   NSFontAttributeName:[UIFont fontWithName:@"HoeflerText-Black" size:24],
   NSExpansionAttributeName:@0.3,
   NSKernAttributeName:@-4
   } range:NSMakeRange(0,1)];
self.tv.attributedText = content2;

(I don’t know why I can’t get the “o” to come any closer to the “F” in Figure 10-5.)

Carrying on from the previous example, I’ll once again construct a paragraph style and add it to the first character. My paragraph style illustrates full justification and automatic hyphenation (Figure 10-6):

NSMutableParagraphStyle* para2 = [NSMutableParagraphStyle new];
para2.headIndent = 10;
para2.firstLineHeadIndent = 10;
para2.tailIndent = -10;
para2.lineBreakMode = NSLineBreakByWordWrapping;
para2.alignment = NSTextAlignmentJustified;
para2.lineHeightMultiple = 1.2;
para2.hyphenationFactor = 1.0;
[content2 addAttribute:NSParagraphStyleAttributeName
                 value:para2 range:NSMakeRange(0,1)];
self.tv.attributedText = content2;

Now we come to the Really Amazing Part. I can make a single attributed string consisting of both paragraphs, and a single text view can portray it (Figure 10-7):

int end = content.length;
[content replaceCharactersInRange:NSMakeRange(end, 0) withString:@"\n"];
[content appendAttributedString:content2];
self.tv.attributedText = content;

Tab stops are new in iOS 7. A tab stop is an NSTextTab, the initializer of which lets you set its location (points from the left edge) and alignment. An options dictionary lets you set the tab stop’s column terminator characters; a common use is to create a decimal tab stop, for aligning currency values at their decimal point. The key, in that case, is NSTabColumnTerminatorsAttributeName; you can obtain a value appropriate to a given NSLocale by calling NSTextTab’s class method columnTerminatorsForLocale:.

Here’s an example (Figure 10-8); I have deliberately omitted the “0” from the end of the second currency value, to prove that the tab stop really is aligning the numbers at their decimal points:

NSString* s = @"Onions\t$2.34\nPeppers\t$15.2\n";
NSMutableParagraphStyle* p = [NSMutableParagraphStyle new];
NSMutableArray* tabs = [NSMutableArray new];
NSCharacterSet* terms =
    [NSTextTab columnTerminatorsForLocale:[NSLocale currentLocale]];
NSTextTab* tab =
    [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentRight
         location:170 options:@{NSTabColumnTerminatorsAttributeName:terms}];
[tabs addObject:tab];
p.tabStops = tabs;
p.firstLineHeadIndent = 20;
NSMutableAttributedString* mas =
    [[NSMutableAttributedString alloc] initWithString:s
    attributes:@{
        NSFontAttributeName:[UIFont fontWithName:@"GillSans" size:15],
        NSParagraphStyleAttributeName:p
    }];
self.tv.attributedText = mas;

Text attachments are also new in iOS 7. A text attachment is basically an inline image. To make one, you need an instance of NSTextAttachment initialized with image data; the easiest way to do this in iOS 7 is to start with a UIImage and assign directly to the NSTextAttachment’s image property. You must also give the NSTextAttachment a nonzero bounds; the image will be scaled to the size of the bounds you provide, and a zero origin places the image on the text baseline.

A text attachment is attached to an NSAttributedString using the NSAttachmentAttributeName key; the text attachment itself is the value. The range of the string that has this attribute must be a special nonprinting character, NSAttachmentCharacter (0xFFFC). The simplest way to arrange that is to call the NSAttributedString class method attributedStringWithAttachment:; you hand it an NSTextAttachment and it hands you an attributed string consisting of the NSAttachmentCharacter with the NSAttachmentAttributeName attribute set to that text attachment. You can then insert this attributed string into your own attributed string at the point where you want the image to appear.

To illustrate, I’ll add an image of onions and an image of peppers just after the words “Onions” and “Peppers” in the attributed string (mas) that I created in the previous example (Figure 10-9):

UIImage* onions = // ...
UIImage* peppers = // ...
NSTextAttachment* onionatt = [NSTextAttachment new];
onionatt.image = onions;
onionatt.bounds =
    CGRectMake(0,-5,onions.size.width,onions.size.height);
NSAttributedString* onionattchar =
    [NSAttributedString attributedStringWithAttachment:onionatt];
NSTextAttachment* pepperatt = [NSTextAttachment new];
pepperatt.image = peppers;
pepperatt.bounds =
    CGRectMake(0,-1,peppers.size.width,peppers.size.height);
NSAttributedString* pepperattchar =
    [NSAttributedString attributedStringWithAttachment:pepperatt];
NSRange r = [[mas string] rangeOfString:@"Onions"];
[mas insertAttributedString:onionattchar atIndex:(r.location + r.length)];
r = [[mas string] rangeOfString:@"Peppers"];
[mas insertAttributedString:pepperattchar atIndex:(r.location + r.length)];
self.tv.attributedText = mas;

Although attributes are applied to ranges, they actually belong to each individual character. Thus we can coherently modify just the string part of a mutable attributed string. The key method here is replaceCharactersInRange:withString:, which can be used to replace characters with a plain string or, using a zero range length, to insert a plain string at the start, middle, or end of an attributed string (as demonstrated in the preceding code). The rule is:

You can query an attributed string about its attributes one character at a time — asking either about all attributes at once (attributesAtIndex:effectiveRange:) or about a particular attribute by name (attribute:atIndex:effectiveRange:). In those methods, the effectiveRange parameter is a pointer to an NSRange variable, which will be set by indirection to the range over which this same attribute value, or set of attribute values, applies:

NSRange range;
NSDictionary* d =
    [content attributesAtIndex:content.length-1 effectiveRange:&range];

Because style runs are something of an artifice, however, you might not end up with what you would think of as the entire style run. The methods with longestEffectiveRange: in their names, on the other hand, do (at the cost of some efficiency) work out the entire style run for you. In practice, you typically don’t need the entire style run range, because you’re cycling through ranges; you want to do that as fast as possible, and speed matters more than getting the longest effective range every time.

In this example, I start with the combined two-paragraph attributed string constructed in the previous examples, and change all the size 15 material to Arial Bold 20. I don’t care whether I’m handed longest effective ranges (and my code explicitly says so); I just want to cycle efficiently:

[content enumerateAttribute:NSFontAttributeName
    inRange:NSMakeRange(0,content.length)
    options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
    usingBlock:^(id value, NSRange range, BOOL *stop)
{
    UIFont* font = value;
    if (font.pointSize == 15)
        [content addAttribute:NSFontAttributeName
                        value:[UIFont fontWithName: @"Arial-BoldMT" size:20]
                        range:range];
}];

You can draw an attributed string directly, without hosting it in a built-in interface object, and sometimes this will prove to be the most reliable approach. Just as an NSString can be drawn into a rect with drawInRect:withFont: and related methods, an NSAttributedString can be drawn with drawAtPoint:, drawInRect:, and drawWithRect:options:context:.

Here, I draw an attributed string into an image (which might then be displayed by an image view):

UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
[[UIColor whiteColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
[content drawInRect:rect]; // draw attributed string
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Similarly, you can draw an attributed string directly in a UIView’s drawRect:. That, in fact, is how the TidBITS News app works (Figure 6-1): each table view cell portrays a single attributed string consisting of the article title and the article summary.

I’ll describe how the attributed string is drawn in the TidBITS News app. The cell’s contentView is completely occupied by a custom UIView class that I call StringDrawer; it has a public attributedText property. In tableView:cellForRowAtIndexPath:, I set that property by calling a utility method that generates this cell’s attributed string by consulting my data model:

cell.drawer.attributedText = [self attributedStringForIndexPath: indexPath];

StringDrawer’s drawRect: draws its attributedText:

- (void)drawRect:(CGRect)rect {
    CGRect r = CGRectOffset(rect, 0, 2); // shoved down a little from top
    [self.attributedText drawWithRect:r
        options:NSStringDrawingTruncatesLastVisibleLine |
                NSStringDrawingUsesLineFragmentOrigin
        context:nil];
}

Note the options: argument in that code. I want an ellipsis at the end of the second paragraph if the whole thing doesn’t fit in the given rect. This can’t be achieved using NSLineBreakByTruncatingTail, which truncates the first line of the paragraph to which it is applied. Therefore, I’m using drawWithRect:options:context:, instead of simple drawInRect:, because it allows me to specify the option NSStringDrawingTruncatesLastVisibleLine. However, I must then also specify NSStringDrawingUsesLineFragmentOrigin; otherwise, the string is drawn with its baseline at the rect origin (so that it appears above that rect) and it doesn’t wrap. The rule is that NSStringDrawingUsesLineFragmentOrigin is the implicit default for simple drawInRect:, but with drawWithRect:options:context: you must specify it explicitly.

To derive the height of the cell, I also measure the attributed string beforehand, in tableView:heightForRowAtIndexPath:. Again, the option NSStringDrawingUsesLineFragmentOrigin is crucial; without it, the measured text doesn’t wrap and the returned height will be very small. New in iOS 7, the documentation warns that the returned height can be fractional and that you should round up with the ceil function if the height of a view is going to depend on this result:

CGRect r =
    [s boundingRectWithSize:CGSizeMake(320,10000)
        options:NSStringDrawingUsesLineFragmentOrigin context:nil];
CGFloat result = ceil(r.size.height);

The context: parameter of drawWithRect:options:context: and boundingRectWithSize:options:context: lets you attach an instance of NSStringDrawingContext. This simple class tells you where you just drew. With a plain NSString, you derive this information from the return value of the drawing command; for example, drawInRect:withFont: returns a CGSize telling you the size of the drawn string. But drawWithRect:options:context: has no return value. Instead, if you attach an NSStringDrawingContext, its totalBounds property tells you, after you draw, the bounds of the drawn string.

A label (UILabel) is a simple built-in interface object for displaying strings. I listed some of its chief properties in Chapter 8 (in Built-In Cell Styles).

If you’re displaying a plain NSString in a label, by way of the label’s text property, then you are likely also to set its font, textColor, and textAlignment properties, and possibly its shadowColor and shadowOffset properties. The label’s text can have an alternate highlightedTextColor, to be used when its highlighted property is YES — as happens, for example, when the label is in a selected cell of a table view.

On the other hand, if you’re using an NSAttributedString, then you’ll set just the label’s attributedText property and let the attributes dictate things like color, alignment, and shadow. Those other UILabel properties do mostly still work, but they’re going to change the attributes of your entire attributed string, in ways that you might not intend. Setting the text of a UILabel that has attributedText will basically eliminate the attributes. The highlightedTextColor property does not work on the attributedText.

UILabel line breaking (wrapping) and truncation behavior, which applies to both single-line and multiline labels, is determined by the lineBreakMode (of the label or the attributed string). Your options are:

The default line break mode for a new label is NSLineBreakByTruncatingTail. But the default line break mode for an attributed string’s NSParagraphStyle is NSLineBreakByWordWrapping.

If a label is too small for its text, the entire text won’t show. If a label is too big for its text, the text is vertically centered in the label, with white space above and below, which may be undesirable. You might like to shrink or grow a label to fit its text.

If you’re not using autolayout, in most simple cases sizeToFit will do exactly the right thing; I believe that behind the scenes it is calling boundingRectWithSize:​options:​context:.

If you’re using autolayout, a label will correctly configure its own intrinsicContentSize automatically, based on its contents — and therefore, all other things being equal, will size itself to fit its contents with no code at all. Every time you reconfigure the label in a way that affects its contents (setting its text, changing its font, setting its attributed text, and so forth), the label automatically invalidates and recalculates its intrinsic content size.

In the case of a short single-line label, you might give the label no width or height constraints; you’ll constrain its position, but you’ll let the label’s intrinsicContentSize provide both the label’s width and its height.

For a multiline label, it is more likely that you’ll want to dictate the label’s width, while letting the label’s height change automatically to accommodate its contents. There are two ways to do this:

If a label’s width is to be permitted to vary because of constraints, you can tell it recalculate its height to fit its contents by setting its preferredMaxLayoutWidth to its actual width. For example, consider a label whose left and right edges are both pinned to the superview. And imagine that the superview’s width can change, thus changing the width of the label. (For example, perhaps the superview is the app’s main view, and the app’s interface is permitted to rotate, thus changing the main view’s width to match the new orientation of the screen.) Here is the code for a UILabel subclass that will respond to that situation by resizing its own height automatically to fit its contents:

@implementation MySelfAdjustingLabel
-(void)layoutSubviews {
    [super layoutSubviews];
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}
@end

Alternatively, you can change a label’s preferredMaxLayoutWidth in the view controller’s viewDidLayoutSubviews, but then you may need to use delayed performance to wait until the label’s width has finished adjusting itself:

- (void)viewDidLayoutSubviews {
    // wait until *after* constraint-based layout has finished
    // that way, the label's width is correct when this code executes
    dispatch_async(dispatch_get_main_queue(), ^{
        self.theLabel.preferredMaxLayoutWidth =
            self.theLabel.bounds.size.width;
    });
}

Instead of letting a label grow, you can elect to permit its text font size to shrink if this would allow more of the text to fit. How the text is repositioned when the font size shrinks is determined by the label’s baselineAdjustment property. The conditions under which this feature operates are:

A text field (UITextField) portrays just a single line of text; any line break characters in its text are treated as spaces. It has many of the same properties as a label. You can can provide it with a plain NSString, setting its text, font, textColor, and textAlignment, or provide it with an attributed string, setting its attributedText. New in iOS 7, you can learn a text field’s overall text attributes as an attributes dictionary through its defaultTextAttributes property. (Under the hood, the text is always attributed text, so the displayed text can end up as a combination of, say, the attributedText and the textColor.)

Under autolayout, a text field’s intrinsicContentSize will attempt to set its width to fit its contents; if its width is fixed, you can set its adjustsFontSizeToFitWidth and minimumFontSize properties to allow the text size to shrink somewhat.

Text that is too long for the text field is displayed with an ellipsis at the end. A text field has no lineBreakMode, but you can change the position of the ellipsis by assigning the text field an attributed string with different truncation behavior, such as NSLineBreakByTruncatingHead. When long text is being edited, the ellipsis (if any) is removed, and the text shifts horizontally to show the insertion point.

Regardless of whether you originally supplied a plain string or an attributed string, if the text field’s allowsEditingTextAttributes property is YES, the user, when editing in the text field, can summon a menu toggling the selected text’s bold, italics, or underline features. (Oddly, there’s no way to set this property in a nib.)

A text field has a placeholder property, which is the text that appears faded within the text field when it has no text (its text or attributedText has been set to nil, or the user has removed all the text); the idea is that you can use this to suggest to the user what the text field is for. It has a styled text alternative, attributedPlaceholder; the runtime will apply an overall light gray color to your attributed string.

If a text field’s clearsOnBeginEditing property is YES, it automatically deletes its existing text (and displays the placeholder) when editing begins within it. If a text field’s clearsOnInsertion property is YES, then when editing begins within it, the text remains, but is invisibly selected, and will be replaced by the user’s typing.

A text field’s border drawing is determined by its borderStyle property. Your options are:

You can supply a background image (background); if you combine this with UITextBorderStyleNone, or if the image has no transparency, you thus get to supply your own border — unless the borderStyle is UITextBorderStyleRoundedRect, in which case the background is ignored. The image is automatically resized as needed (and you will probably supply a resizable image). A second image (disabledBackground) can be displayed when the text field’s enabled property, inherited from UIControl, is NO. The user can’t interact with a disabled text field, but without a disabledBackground image, the user may lack any visual clue to this fact. You can’t set the disabledBackground unless you have also set the background.

A text field may contain one or two ancillary overlay views, its leftView and rightView, and possibly a Clear button (a gray circle with a white X). The automatic visibility of each of these is determined by the leftViewMode, rightViewMode, and clearViewMode, respectively. The view mode values are:

Depending on what sort of view you use, your leftView and rightView may have to be sized manually so as not to overwhelm the text view contents. If a right view and a Clear button appear at the same time, the right view may cover the Clear button unless you reposition it.

The positions and sizes of any of the components of the text field can be set in relation to the text field’s bounds by overriding the appropriate method in a subclass:

You can also override in a subclass the methods drawTextInRect: and drawPlaceholderInRect:. You should either draw the specified text or call super to draw it; if you do neither, the text won’t appear. Both these methods are called with a parameter whose size is the dimensions of the text field’s text area, but whose origin is {0,0}. In effect what you’ve got is a graphics context for just the text area; any drawing you do outside the given rectangle will be clipped.

Making the onscreen simulated keyboard appear when the user taps in a text field is no work at all — it’s automatic, as you’ve probably observed already. Making the keyboard vanish again, on the other hand, can be a bit tricky. (Another problem is that the keyboard can cover the text field that the user just tapped in; I’ll talk about that in a moment.)

The presence or absence of the keyboard, and a text field’s editing state, are intimately tied to one another, and to the text field’s status as the first responder:

Thus, you can programmatically control the presence or absence of the keyboard, together with a text field’s editing state, by way of the text field’s first responder status:

Becoming first responder

To make the insertion point appear within a text field and to cause the keyboard to appear, you send becomeFirstResponder to that text field.

You won’t often have to do that; more often, the user will tap in a text field and it will become first responder automatically. Still, sometimes it’s useful to make a text field the first responder programmatically; an example appeared in Chapter 8 (Inserting Table Items).

Resigning first responder

To make a text field stop being edited and to cause the keyboard to disappear, you send resignFirstResponder to that text field. (Actually, resignFirstResponder returns a BOOL, because a responder might return NO to indicate that for some reason it refuses to obey this command.)

Alternatively, send the UIView endEditing: method to the first responder or any superview (including the window) to ask or compel the first responder to resign first responder status.

In a view presented in the UIModalPresentationFormSheet style on the iPad (Chapter 6), the keyboard, by default, does not disappear when a text field resigns first responder status. This is presumably because a form sheet is intended primarily for text input, so the keyboard is felt as accompanying the form as a whole, not individual text fields. Optionally, you can prevent this exceptional behavior: in your UIViewController subclass, override disablesAutomaticKeyboardDismissal to return NO.

There is no simple way to learn what view is first responder! This is very odd, because a window surely knows what its first responder is — but it won’t tell you. There’s a method isFirstResponder, but you’d have to send it to every view in a window until you find the first responder. One workaround is to store a reference to the first responder yourself, typically in your implementation of the text field delegate’s textFieldDidBeginEditing:.

Once the user has tapped in a text field and the keyboard has automatically appeared, how is the user supposed to get rid of it? On the iPad, the keyboard typically contains a special button that dismisses the keyboard. But on the iPhone, this is an oddly tricky issue. You would think that the “return” button in the keyboard would dismiss the keyboard; but, of itself, it doesn’t.

One solution is to be the text field’s delegate and to implement a text field delegate method, textFieldShouldReturn:. When the user taps the Return key in the keyboard, we hear about it through this method, and we tell the text field to resign its first responder status, which dismisses the keyboard:

- (BOOL)textFieldShouldReturn: (UITextField*) tf {
    [tf resignFirstResponder];
    return YES;
}

I’ll provide a more automatic solution later in this chapter.

The keyboard has a position “docked” at the bottom of the screen. This may cover the text field in which the user wants to type, even if it is first responder. On the iPad, this may not be an issue, because the user can “undock” the keyboard (possibly also splitting and shrinking it) and slide it up and down the screen freely. On the iPhone, you’ll typically want to do something to reveal the text field.

To help with this, you can register for keyboard-related notifications:

Those notifications all have to do with the docked position of the keyboard. On the iPhone, keyboard docking and keyboard visibility are equivalent: the keyboard is visible if and only if it is docked. On the iPad, the keyboard is said to “show” if it is being docked, whether that’s because it is appearing from offscreen or because the user is docking it; and it is said to “hide” if it is undocked, whether that’s because it is moving offscreen or because the user is undocking it.

Two additional notifications are sent both when the keyboard enters and leaves the screen and (on the iPad) when the user drags it, splits or unsplits it, and docks or undocks it:

The notification’s userInfo dictionary contains information about the keyboard describing what it will do or has done, under these keys:

Thus, to a large extent, you can coordinate your actions with those of the keyboard. In particular, by looking at the UIKeyboardFrameEndUserInfoKey, you know what position the keyboard is moving to; you can compare this with the screen bounds to learn whether the keyboard will now be on or off the screen and, if it will now be on the screen, you can see whether it will cover a text field.

Finding a strategy for dealing with the keyboard’s presence depends on the needs of your particular app. I’ll concentrate on the most universal case, where the keyboard moves into and out of docked position and we detect this with UIKeyboardWillShowNotification and UIKeyboardWillHideNotification. What should we do if, when the keyboard appears, it covers the text field being edited?

One natural-looking approach is to slide the entire interface upward as the keyboard appears. To make this easy, you might start with a view hierarchy like this: the root view contains a transparent view that’s the same size as the root view; everything else is contained in that transparent view. The transparent view’s purpose is to host the rest of the interface; if we slide it upward, the whole interface will slide upward.

Here’s an implementation involving constraints. The transparent view, which I’ll called the sliding view, is pinned by constraints at the top and bottom to its superview with a constant of 0, and we have outlets to those constraints. We also have an outlet to the sliding view itself, and we’ve got a property prepared to hold the first responder:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet NSLayoutConstraint* topConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint* bottomConstraint;
@property (weak, nonatomic) IBOutlet UIView *slidingView;
@property (nonatomic, weak) UIView* fr;
@end

In our view controller’s viewDidLoad, we register for the keyboard notifications:

[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(keyboardShow:)
    name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(keyboardHide:)
    name:UIKeyboardWillHideNotification object:nil];

We are the delegate of the various text fields in our interface. When one of them starts editing, we keep a reference to it as first responder:

- (void)textFieldDidBeginEditing:(UITextField *)tf {
    self.fr = tf; // keep track of first responder
}

As I suggested in the previous section, we also dismiss the keyboard by resigning first responder when the user taps the Return button in the keyboard:

- (BOOL)textFieldShouldReturn: (UITextField*) tf {
    [tf resignFirstResponder];
    self.fr = nil;
    return YES;
}

As the keyboard threatens to appear, we examine where its top will be. If the keyboard will cover the text field that’s about to be edited, we animate the sliding view upward to compensate, by changing the constant value of the constraints that pin its top and bottom, gearing our animation to that of the keyboard. The keyboard’s frame comes to us in window/screen coordinates, so it is necessary to convert it to our sliding view’s coordinates in order to make sense of it:

- (void) keyboardShow: (NSNotification*) n {
    NSDictionary* d = [n userInfo];
    CGRect r = [d[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    r = [self.slidingView convertRect:r fromView:nil];
    CGRect f = self.fr.frame;
    CGFloat y =
        CGRectGetMaxY(f) + r.size.height -
        self.slidingView.bounds.size.height + 5;
    NSNumber* duration = d[UIKeyboardAnimationDurationUserInfoKey];
    NSNumber* curve = d[UIKeyboardAnimationCurveUserInfoKey];
    if (r.origin.y < CGRectGetMaxY(f)) {
        [UIView animateWithDuration:duration.floatValue
                              delay:0
                            options:curve.integerValue << 16
                         animations:^{
            self.topConstraint.constant = -y;
            self.bottomConstraint.constant = y;
            [self.view layoutIfNeeded];
        } completion:nil];
    }
}

When the keyboard disappears, we reverse the procedure:

- (void) keyboardHide: (NSNotification*) n {
    NSNumber* duration = n.userInfo[UIKeyboardAnimationDurationUserInfoKey];
    NSNumber* curve = n.userInfo[UIKeyboardAnimationCurveUserInfoKey];
    [UIView animateWithDuration:duration.floatValue
                          delay:0
                        options:curve.integerValue << 16
                     animations:^{
        self.topConstraint.constant = 0;
        self.bottomConstraint.constant = 0;
        [self.view layoutIfNeeded];
    } completion:nil];
}

Instead of moving the sliding view itself, we could instead shift its bounds origin. If we’re going to do that, we might as well make the sliding view a scroll view — a view that already knows all about shifting its bounds origin! This approach has two notable advantages over the preceding approach:

Let’s imitate UITableViewController’s behavior with a scroll view containing text fields. In viewDidLoad, we register for keyboard notifications, and we are the delegate of any text fields, exactly as in the previous example. When the keyboard appears, we store the current content offset, content inset, and scroll indicator insets; then we alter them. I set the scroll view’s bounds directly, rather than calling setContentOffset:​animated:, because I want to match the keyboard animation:

- (void) keyboardShow: (NSNotification*) n {
    self->_oldContentInset = self.scrollView.contentInset;
    self->_oldIndicatorInset = self.scrollView.scrollIndicatorInsets;
    self->_oldOffset = self.scrollView.contentOffset;
    NSDictionary* d = [n userInfo];
    CGRect r = [[d objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    r = [self.scrollView convertRect:r fromView:nil];
    CGRect f = self.fr.frame;
    CGFloat y =
        CGRectGetMaxY(f) + r.size.height -
        self.scrollView.bounds.size.height + 5;
    if (r.origin.y < CGRectGetMaxY(f)) {
        NSNumber* duration = d[UIKeyboardAnimationDurationUserInfoKey];
        NSNumber* curve = d[UIKeyboardAnimationCurveUserInfoKey];
        [UIView animateWithDuration:duration.floatValue
                              delay:0
                            options:curve.integerValue << 16
                         animations:^{
                             CGRect b = self.scrollView.bounds;
                             b.origin = CGPointMake(0, y);
                             self.scrollView.bounds = b;
                         } completion: nil];
    }
    UIEdgeInsets insets = self.scrollView.contentInset;
    insets.bottom = r.size.height;
    self.scrollView.contentInset = insets;
    insets = self.scrollView.scrollIndicatorInsets;
    insets.bottom = r.size.height;
    self.scrollView.scrollIndicatorInsets = insets;
}

When the keyboard disappears, we restore the saved values:

- (void) keyboardHide: (NSNotification*) n {
    NSNumber* duration = n.userInfo[UIKeyboardAnimationDurationUserInfoKey];
    NSNumber* curve = n.userInfo[UIKeyboardAnimationCurveUserInfoKey];
    [UIView animateWithDuration:duration.floatValue
                          delay:0
                        options:curve.integerValue << 16
                     animations:^{
                         CGRect b = self.scrollView.bounds;
                         b.origin = self->_oldOffset;
                         self.scrollView.bounds = b;
                         self.scrollView.scrollIndicatorInsets =
                             self->_oldIndicatorInset;
                         self.scrollView.contentInset =
                             self->_oldContentInset;
                     } completion:nil];
}

New in iOS 7, UIScrollView’s keyboardDismissMode provides some new ways letting the user dismiss the keyboard. The options are:

A UITextField implements the UITextInputTraits protocol, which defines properties on the UITextField that you can set to determine how the keyboard will look and how typing in the text field will behave. (These properties can also be set in the nib.) For example, you can set the keyboardType to UIKeyboardTypePhonePad to make the keyboard for this text field consist of digits only. You can set the returnKeyType to determine the text of the Return key (if the keyboard is of a type that has one). New in iOS 7, you can give the keyboard a dark or light shade (keyboardAppearance). You can turn off autocapitalization (autocapitalizationType) or autocorrection (autocorrectionType), make the Return key disable itself if the text field has no content (enablesReturnKeyAutomatically), and make the text field a password field (secureTextEntry). You can even supply your own keyboard or other input mechanism by setting the text field’s inputView.

You can attach an accessory view to the top of the keyboard by setting the text field’s inputAccessoryView. In this example, the accessory view has been loaded from a nib and is available through a property, accessoryView. When editing starts, we configure the keyboard as we store our reference to the text field:

- (void)textFieldDidBeginEditing:(UITextField *)tf {
    self.fr = tf; // keep track of first responder
    tf.inputAccessoryView = self.accessoryView;
}

We have an NSArray property populated with references to all our text fields (this might be an appropriate use of an outlet collection). The accessory view contains a Next button, whose action method is doNextField:. When the user taps the button, we move editing to the next text field:

- (void) doNextButton: (id) sender {
    NSUInteger ix = [self.textFields indexOfObject:self.fr];
    if (ix == NSNotFound)
        return; // shouldn't happen
    ix++;
    if (ix >= [self.textFields count])
        ix = 0;
    UIView* v = self.textFields[ix];
    [v becomeFirstResponder];
}

The user can control the localization of the keyboard character set in the Settings app, either through a choice of the system’s base language or by enabling additional “international keyboards.” In the latter case, the user can switch among keyboard character sets while the keyboard is showing. But, as far as I can tell, your code can’t make this choice; you cannot, for example, force a certain text field to display the Cyrillic keyboard. You can ask the user to switch keyboards manually, but if you really want a particular keyboard to appear regardless of the user’s settings and behavior, you’ll have to create it yourself and provide it as the inputView.

As editing begins and proceeds in a text field, a sequence of messages is sent to the text field’s delegate, adopting the UITextFieldDelegate protocol. (Some of these messages are also available as notifications.) Using them, you can customize the text field’s behavior during editing:

textFieldShouldBeginEditing:
Return NO to prevent the text field from becoming first responder.
textFieldDidBeginEditing:
UITextFieldTextDidBeginEditingNotification
The text field has become first responder.
textFieldShouldClear:
Return NO to prevent the operation of the Clear button or of automatic clearing on entry (clearsOnBeginEditing). This event is not sent when the text is cleared because clearsOnInsertion is YES, because the user is not clearing the text but rather changing it.
textFieldShouldReturn:
The user has tapped the Return button in the keyboard. We have already seen that this can be used as a signal to dismiss the keyboard.
textField:shouldChangeCharactersInRange:replacementString:

Sent when the user changes the text in the field by typing or pasting, or by backspacing or cutting (in which case the replacement string will have zero length). Return NO to prevent the proposed change; you can substitute text by changing the text field’s text directly (there is no circularity, as this delegate method is not called when you do that).

In this example, the user can enter only lowercase characters:

-(BOOL)textField:(UITextField *)textField
        shouldChangeCharactersInRange:(NSRange)range
        replacementString:(NSString *)string {
    NSString* lc = [string lowercaseString];
    textField.text =
        [textField.text stringByReplacingCharactersInRange:range
                                                withString:lc];
    return NO;
}

Another use of textField:shouldChangeCharactersInRange:replacementString: is to take advantage of the typingAttributes property to set the attributes of the text the user is about to enter. In this example, I’ll set the user’s text to be red underlined (in iOS 6, due to a bug, this code didn’t work):

NSDictionary* d = textField.typingAttributes;
NSMutableDictionary* md = [d mutableCopy];
[md addEntriesFromDictionary:
    @{NSForegroundColorAttributeName:[UIColor redColor],
      NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle)}];
textField.typingAttributes = md;

It is common practice to implement textField:shouldChangeCharactersInRange:replacementString: as a way of learning that the text has been changed, even if you then always return YES. This method is not called when the user changes text styling through the Bold, Italics, or Underline menu items.

UITextFieldTextDidChangeNotification corresponds loosely.

textFieldShouldEndEditing:
Return NO to prevent the text field from resigning first responder (even if you just sent resignFirstResponder to it). You might do this, for example, because the text is invalid or unacceptable in some way. The user will not know why the text field is refusing to end editing, so the usual thing is to put up an alert (Chapter 13) explaining the problem.
textFieldDidEndEditing:
UITextFieldTextDidEndEditingNotification
The text field has resigned first responder. See Chapter 8 (Editable Content in Table Items) for an example of using textFieldDidEndEditing: to fetch the text field’s current text and store it in the model.

A text field is also a control (UIControl; see also Chapter 12). This means you can attach a target–action pair to any of the events that it reports in order to receive a message when that event occurs:

In general, you’re more likely to treat a text field as a text field (through its delegate messages) than as a control (through its control events). However, the Did End on Exit event message has an interesting property: it provides an alternative way to dismiss the keyboard when the user taps a text field keyboard’s Return button. If there is a Did End on Exit target–action pair for this text field, then if the text field’s delegate does not return NO from textFieldShouldReturn:, the keyboard will be dismissed automatically when the user taps the Return key. (The action handler for Did End on Exit doesn’t actually have to do anything.)

This suggests the following trick for getting automatic keyboard dismissal with no code at all. In the nib, edit the First Responder proxy object in the Attributes inspector, adding a new First Responder Action; let’s call it dummy:. Now hook the Did End on Exit event of the text field to the dummy: action of the First Responder proxy object. That’s it! Because the text field’s Did End on Exit event now has a target–action pair, the text field automatically dismisses its keyboard when the user taps Return; there is no penalty for not finding a handler for a message sent up the responder chain, so the app doesn’t crash even though there is no implementation of dummy: anywhere.

Alternatively, you can implement the same trick in code:

[textField addTarget:nil action:@selector(dummy:)
    forControlEvents:UIControlEventEditingDidEndOnExit];

A disabled text field emits no delegate messages or control events.

When the user double-taps or long-presses in a text field, the menu appears. It contains menu items such as Select, Select All, Paste, Copy, Cut, and Replace; which menu items appear depends on the circumstances. The menu can be customized; the key facts you need to know are these:

As an example, we’ll devise a text field whose menu includes our own menu item, Expand. I’m imagining here, for instance, a text field where the user can select a U.S. state two-letter abbreviation (such as “CA”) and can then summon the menu and tap Expand to replace it with the state’s full name (such as “California”).

At some point before the user can tap in an instance of our UITextField subclass, we modify the global menu:

UIMenuItem *mi = [[UIMenuItem alloc] initWithTitle:@"Expand"
                                            action:@selector(expand:)];
UIMenuController *mc = [UIMenuController sharedMenuController];
mc.menuItems = @[mi];

In a UITextField subclass, we implement canPerformAction:withSender: to govern the contents of the menu. The reason for putting this code in a subclass is to guarantee that this implementation of canPerformAction:withSender: will be called when an instance of this subclass is first responder, but at no other time. Let’s presume that we want our Expand menu item to be present only if the selection consists of a two-letter state abbreviation. UITextField itself provides no way to learn the selected text, but it conforms to the UITextInput protocol, which does:

- (BOOL) canPerformAction:(SEL)action withSender: (id) sender {
    if (action == @selector(expand:)) {
        NSString* s = [self textInRange:self.selectedTextRange];
        return (s.length == 2 && [self.class stateForAbbrev: s]);
    }
    return [super canPerformAction:action withSender:sender];
}

When the user chooses the Expand menu item, the expand: message is sent up the responder chain. We catch it in our UITextField subclass and obey it by replacing the selected text with the corresponding state name:

- (void) expand: (id) sender {
    NSString* s = [self textInRange:self.selectedTextRange];
    s = [self stateForAbbrev:s]; // left as an exercise for the reader
    [self replaceRange:self.selectedTextRange withText:s];
}

We can also implement the selector for, and thus modify the behavior of, any of the standard menu items. For example, I’ll implement copy: and modify its behavior. First we call super to get standard copying behavior; then we modify what’s now on the pasteboard:

- (void) copy: (id) sender {
    [super copy: sender];
    UIPasteboard* pb = [UIPasteboard generalPasteboard];
    NSString* s = pb.string;
    // ... alter s here ....
    pb.string = s;
}

(Implementing the selectors for toggleBoldface:, toggleItalics:, and toggleUnderline: is probably the best way to get an event when the user changes these attributes.)

A text view (UITextView) is a scroll view subclass (UIScrollView); it is not a control. Many of its properties are similar to those of a text field:

A text view provides (official) information about, and control of, its selection: it has a selectedRange property which you can get and set, along with a scrollRangeToVisible: method so that you can scroll in terms of a range of its text.

A text view’s delegate messages (UITextViewDelegate protocol) and notifications, too, are similar to those of a text field. The following delegate methods (and notifications) should have a familiar ring:

Some differences are:

New in iOS 7, a text view’s delegate can also decide how to respond when the user taps on a text attachment or a link. The text view must have its selectable property (new in iOS 7) set to YES, and its editable property set to NO:

By returning NO from either of those methods, you can substitute your own response, effectively treating the image or URL as a button.

A text view also has a dataDetectorTypes property; this, too, if the text view is selectable but not editable, allows text of certain types, specified as a bitmask (and presumably located using NSDataDetector), to be treated as tappable links; the types are:

New in iOS 7, the delegate’s implementation of textView:shouldInteractWithURL:inRange: catches data detector taps as well, so you can prevent the default behavior and substitute your own. You can distinguish a phone number through the URL’s scheme (it will be @"tel"), but an address or calendar event will be opaque (the scheme is @"x-apple-data-detectors") and returning NO probably makes no sense. The delegate method doesn’t distinguish a tap from a long press for you.

A text view is a scroll view, so everything you know about scroll views applies (see Chapter 7). It has, by default, no border, because a scroll view has no border. It can be user-scrollable or not.

A text view’s contentSize is maintained for you, automatically, as the text changes, so as to contain the text exactly; thus, if the text view is scrollable, the user can see any of its text. You can track changes to the content size by tracking changes to the text (in the delegate’s textViewDidChange:, for example). A common reason for doing so is to implement a self-sizing text view, that is, a text view that adjusts its height automatically to embrace the amount of text it contains. In this example, we have an outlet to the text view’s internal height constraint:

- (void)textViewDidChange:(UITextView *)textView {
    self.heightConstraint.constant = textView.contentSize.height;
}

The fact that a text view is a scroll view comes in handy also when the keyboard partially covers a text view. The text view quite often dominates the screen, or a large portion of the screen, and you can respond to the keyboard partially covering it by adjusting the text view’s contentInset, just as we did earlier in this chapter with a scroll view (Text field in a scroll view).

Here’s a fairly straightforward implementation for dealing with what happens when the user taps in the text view to start editing and the keyboard partially covers the text view (self.tv):

-(void)viewDidLoad {
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(keyboardShow:)
        name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(keyboardHide:)
        name:UIKeyboardWillHideNotification object:nil];
}
- (void) keyboardShow: (NSNotification*) n {
    NSDictionary* d = [n userInfo];
    CGRect r = [[d objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    r = [self.tv.superview convertRect:r fromView:nil];
    CGRect f = self.tv.frame;
    CGRect fs = self.tv.superview.bounds;
    CGFloat diff = fs.size.height - f.origin.y - f.size.height;
    CGFloat keyboardTop = r.size.height - diff;
    UIEdgeInsets insets = self.tv.contentInset;
    insets.bottom = keyboardTop;
    self.tv.contentInset = insets;
    insets = self.tv.scrollIndicatorInsets;
    insets.bottom = keyboardTop;
    self.tv.scrollIndicatorInsets = insets;
}

However, in iOS 7 there’s a bug: if the keyboard now covers the spot in the text view where the user tapped, the selection remains hidden behind the keyboard — and calling scrollRangeToVisible: doesn’t help. (In previous versions of iOS, the text view scrolled automatically to reveal the selection.) And there’s another bug: as the user types, the text view doesn’t automatically scroll up to keep newly entered characters above the top of the keyboard. (In previous versions of iOS, it did.)

A possible workaround involves taking advantage of the fact that UITextView implements the UITextInput protocol. For example:

-(void)textViewDidChange:(UITextView *)textView {
    // prevent typed characters from going behind keyboard
    // the keyboard should be doing this for us automatically!
    CGRect r =
        [textView caretRectForPosition:textView.selectedTextRange.end];
    [textView scrollRectToVisible:r animated:NO];
}

Now let’s talk about what happens when the keyboard is dismissed. First of all, how is the keyboard to be dismissed? On the iPad, the virtual keyboard usually contains a button that dismisses the keyboard. But what about the iPhone? The Return key is meaningful for character entry; you aren’t likely to want to misuse it as a way of dismissing the keyboard.

On the iPhone, the interface might well consist of a text view and the keyboard, which is always showing: instead of dismissing the keyboard, the user dismisses the entire interface. For example, in Apple’s Mail app on the iPhone, when the user is composing a message, in what is presumably a presented view controller, the keyboard is present the whole time; the keyboard is dismissed because the user sends or cancels the message and the presented view controller is dismissed.

Alternatively, you can provide interface for dismissing the keyboard explicitly. For example, in Apple’s Notes app, a note alternates between being read fullscreen and being edited with the keyboard present; in the latter case, a Done button appears, and the user taps it to dismiss the keyboard. If there’s no good place to put a Done button in the interface, you could attach an accessory view to the keyboard itself.

Here’s a possible implementation of a Done button’s action method, with resulting dismissal of the keyboard:

- (IBAction)doDone:(id)sender {
    [self.view endEditing:NO];
}
- (void) keyboardHide: (NSNotification*) n {
    NSDictionary* d = [n userInfo];
    NSNumber* curve = d[UIKeyboardAnimationCurveUserInfoKey];
    NSNumber* duration = d[UIKeyboardAnimationDurationUserInfoKey];
    [UIView animateWithDuration:duration.floatValue delay:0
                        options:curve.integerValue << 16
                     animations:
     ^{
         [self.tv setContentOffset:CGPointZero];
     } completion:^(BOOL finished) {
         self.tv.contentInset = UIEdgeInsetsZero;
         self.tv.scrollIndicatorInsets = UIEdgeInsetsZero;
     }];
}

New in iOS 7, Text Kit has been introduced — actually, imported from OS X, where you may already be more familiar with its use than you realize. For example, much of the text-editing “magic” of Xcode is due to Text Kit.

Text Kit is a group of Objective-C classes that are responsible for drawing text; simply put, they turn an NSAttributedString into graphics. You can take advantage of Text Kit to modify text drawing in ways that were possible in previous systems only by dipping down to the low-level C-based world of Core Text (if at all).

A UITextView in iOS 7 provides direct access to the underlying Text Kit engine. It has the following Text Kit–related properties:

When you initialize a text view with a text container, you hand it the entire “stack” of Text Kit instances: a text container, a layout manager, and a text storage. In the simplest and most common case, a text storage has a layout manager, and a layout manager has a text container, thus forming the “stack”. If the text container is a UITextView’s text container, the stack is retained, and the text view is operative. Thus, the simplest case might look like this (and we may suppose that this is in fact what the runtime does when you call UITextView’s initWithFrame:):

CGRect r = // ... frame for the new text view
NSLayoutManager* lm = [NSLayoutManager new];
NSTextStorage* ts = [NSTextStorage new];
[ts addLayoutManager:lm];
NSTextContainer* tc =
    [[NSTextContainer alloc]
        initWithSize:CGSizeMake(r.size.width, CGFLOAT_MAX)];
[lm addTextContainer:tc];
UITextView* tv = [[UITextView alloc] initWithFrame:r textContainer:tc];

Here’s what the three chief Text Kit classes do:

An NSTextContainer has a size, within which the text will be drawn. By default, as in the preceding code, a text view’s text container’s width is the width of the text view, while its height is effectively infinite, allowing the drawing of the text to grow vertically but not horizontally beyond the bounds of the text view, and making it possible to scroll the text vertically.

It also has heightTracksTextView and widthTracksTextView properties, causing the text container to be resized to match changes in the size of the text view — for example, if the text view is resized because of interface rotation. By default, as you might expect, widthTracksTextView is YES (the documentation is wrong about this), while heightTracksTextView is NO: the text fills the width of the text view, and is laid out freshly if the text view’s width changes, but its height remains effectively infinite. The text view itself, of course, configures its own contentSize so that the user can scroll just to the bottom of the existing text.

When you change a text view’s textContainerInset, it modifies its text container’s size to match, as necessary. In the default configuration, this means that it modifies the text container’s width; the top and bottom insets are implemented through the text container’s position within the content rect. Within the text container, additional side margins correspond to the text container’s lineFragmentPadding; the default is 5, but you can change it.

If the text view’s scrollEnabled is NO, then by default its text container’s heightTracksTextView and widthTracksTextView are both YES, and the text container size is adjusted so that the text fills the text view. In that case, you can also set the text container’s lineBreakMode. This works like the line break mode of a UILabel. For example, if the line break mode is NSLineBreakByTruncatingTail, then the last line has an ellipsis at the end (if the text is too long for the text view). You can also set the text container’s maximumNumberOfLines, which is like a UILabel’s numberOfLines. In effect, you’ve turned the text view into a label!

But, of course, a nonscrolling text view isn’t just a label, because you’ve got access to the Text Kit stack that backs it. For example, you can apply exclusion paths to the text container. Figure 10-10 shows a case in point. The text wraps in longer and longer lines, and then in shorter and shorter lines, because there’s an exclusion path on the right side of the text container that’s a rectangle with a large V-shaped indentation.

In Figure 10-10, the text view (self.tv) is initially configured in the view controller’s viewDidLoad:

self.tv.attributedText = // ...
self.tv.textContainerInset = UIEdgeInsetsMake(20, 20, 20, 0);
self.tv.scrollEnabled = NO;

The exclusion path is then drawn and applied in viewDidLayoutSubviews:

-(void)viewDidLayoutSubviews {
    CGSize sz = self.tv.textContainer.size;
    UIBezierPath* p = [UIBezierPath new];
    [p moveToPoint:CGPointMake(sz.width/4.0,0)];
    [p addLineToPoint:CGPointMake(sz.width,0)];
    [p addLineToPoint:CGPointMake(sz.width,sz.height)];
    [p addLineToPoint:CGPointMake(sz.width/4.0,sz.height)];
    [p addLineToPoint:CGPointMake(sz.width,sz.height/2.0)];
    [p closePath];
    self.tv.textContainer.exclusionPaths = @[p];
}

Instead of (or in addition to) an exclusion path, you can subclass NSTextContainer to modify the rectangle in which the layout manager wants to position a piece of text. (Each piece of text is actually a line fragment; I’ll explain in the next section what a line fragment is.) In Figure 10-11, the text is inside a circle.

To achieve the layout shown in Figure 10-11, I set the attributed string’s line break mode to NSLineBreakByCharWrapping (to bring the right edge of each line as close as possible to the circular shape), and constructed the TextKit stack by hand to include an instance of my NSTextContainer subclass. That subclass contains this code, in which I simplemindedly increase each line fragment’s horizontal origin and decrease its width until its top edge fits entirely within a circle:

-(CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect
        atIndex:(NSUInteger)characterIndex
        writingDirection:(NSWritingDirection)baseWritingDirection
        remainingRect:(CGRect *)remainingRect {
    CGRect result =
        [super lineFragmentRectForProposedRect:proposedRect
            atIndex:characterIndex
            writingDirection:baseWritingDirection
            remainingRect:remainingRect];
    CGRect r = CGRectMake(0,0,self.size.width,self.size.height);
    UIBezierPath* circle = [UIBezierPath bezierPathWithOvalInRect:r];
    CGPoint p = result.origin;
    while (![circle containsPoint:p]) {
        p.x += .1;
        result.origin = p;
    }
    CGFloat w = result.size.width;
    p = result.origin;
    p.x += w;
    while (![circle containsPoint:p]) {
        w -= .1;
        result.size.width = w;
        p = result.origin;
        p.x += w;
    }
    return result;
}

Alternative Text Kit Stack Architectures

The default Text Kit stack is one text storage, which has one layout manager, which has one text container. But a text storage can have multiple layout managers, and a layout manager can have multiple text containers. What’s that all about?

If a layout manager has multiple text containers, the overflow from each text container is drawn in the next one. For example, in Figure 10-12, there are two text views; the text has filled the first text view, and has then continued by flowing into and filling the second text view. As far as I can tell, the text views can’t be made editable in this configuration. But clearly this is a way to achieve a multicolumn or multipage layout, or you could use text views of different sizes for a magazine-style layout.

It is possible to achieve that arrangement by disconnecting the layout managers of existing text views from their text containers and rebuilding the stack from below. However, I think it’s probably safer to build the entire stack by hand:

CGRect r = //
CGRect r2 = //
NSAttributedString* mas = //
NSTextStorage* ts1 = [[NSTextStorage alloc] initWithAttributedString:mas];
NSLayoutManager* lm1 = [NSLayoutManager new];
[ts1 addLayoutManager:lm1];
NSTextContainer* tc1 = [[NSTextContainer alloc] initWithSize:r.size];
[lm1 addTextContainer:tc1];
UITextView* tv = [[UITextView alloc] initWithFrame:r textContainer:tc1];
tv.scrollEnabled = NO;
NSTextContainer* tc2 = [[NSTextContainer alloc] initWithSize:r2.size];
[lm1 addTextContainer:tc2];
UITextView* tv2 = [[UITextView alloc] initWithFrame:r2 textContainer:tc2];
tv2.scrollEnabled = NO;

If a text storage has multiple layout managers, then each layout manager is laying out the same text. For example, in Figure 10-13, there are two text views displaying the same text. The remarkable thing is that if you edit one text view, the other changes to match. (That’s how Xcode lets you edit the same code file in different windows, tabs, or panes.)

Again, this arrangement is probably best achieved by building the entire text stack by hand:

CGRect r = //
CGRect r2 = //
NSAttributedString* mas = //
NSTextStorage* ts1 = [[NSTextStorage alloc] initWithAttributedString:mas];
NSLayoutManager* lm1 = [NSLayoutManager new];
[ts1 addLayoutManager:lm1];
NSLayoutManager* lm2 = [NSLayoutManager new];
[ts1 addLayoutManager:lm2];
NSTextContainer* tc1 = [[NSTextContainer alloc] initWithSize:r.size];
NSTextContainer* tc2 = [[NSTextContainer alloc] initWithSize:r2.size];
[lm1 addTextContainer:tc1];
[lm2 addTextContainer:tc2];
UITextView* tv = [[UITextView alloc] initWithFrame:r textContainer:tc1];
UITextView* tv2 = [[UITextView alloc] initWithFrame:r2 textContainer:tc2];

The first thing to know about a layout manager is the geometry in which it thinks. To envision a layout manager’s geometrical world, think in terms of glyphs and line fragments:

A glyph has a location in terms of the line fragment into which it is drawn. A line fragment’s coordinates are in terms of the text container. The layout manager can convert between these coordinate systems, and between text and glyphs. Given a range of text in the text storage, it knows where the corresponding glyphs are drawn in the text container. Conversely, given a location in the text container, it knows what glyph is drawn there and what range of text in the text storage that glyph represents.

What’s missing from that geometry is what, if anything, the text container corresponds to in the real world. A text container is not, itself, a real rectangle in the real world; it’s just a class that tells the layout manager a size to draw into. Making that rectangle meaningful for drawing purposes is up to some other class outside the Text Kit stack. A UITextView, for example, has a text container, which it shares with a layout manager. The text view knows how its own content is scrolled and how the rectangle represented by its text container is inset within that scrolling content. The layout manager, however, doesn’t know anything about that; it sees the text container as a purely theoretical rectangular boundary. (Only when the layout manager actually draws does it make contact with the real world of some graphics context — and it must be told, on those occasions, how the text container’s rectangle is offset within that graphics context.)

To illustrate, I’ll use a TextKit method to learn the index of the first character visible at the top left of a text view (self.tv); I’ll then use NSLinguisticTagger to derive the first word visible at the top left of the text view. I can ask the layout manager what character or glyph corresponds to a certain point in the text container, but what point should I ask about? Translating from the real world to text container coordinates is up to me; I must take into account both the scroll position of the text view’s content and the inset of the text container within that content:

CGPoint off = self.tv.contentOffset;
CGFloat top = self.tv.textContainerInset.top;
CGPoint tctopleft = CGPointMake(0, off.y - top);

Now I’m speaking in terms of text container coordinates, which are layout manager coordinates. One possibility is then to ask directly for the index (in the text storage’s string) of the corresponding character:

NSUInteger ix =
    [self.tv.layoutManager characterIndexForPoint:tctopleft
        inTextContainer:self.tv.textContainer
        fractionOfDistanceBetweenInsertionPoints:nil];

That, however, does not give quite the results one might intuitively expect. If any of a word is poking down from above into the visible area of the text view, that is the word whose first character is returned. I think we intuitively expect, if a word isn’t fully visible, that the answer should be the word that starts the next line, which is fully visible. So I’ll modify that code in a simpleminded way. I’ll obtain the index of the glyph at my initial point; from this, I can derive the rect of the line fragment containing it. If that line fragment is not at least three-quarters visible, I’ll add one line fragment height to the starting point and derive the glyph index again. Then I’ll convert the glyph index to a character index:

NSUInteger ix =
    [self.tv.layoutManager glyphIndexForPoint:tctopleft
        inTextContainer:self.tv.textContainer
        fractionOfDistanceThroughGlyph:nil];
CGRect frag =
    [self.tv.layoutManager lineFragmentRectForGlyphAtIndex:ix
                                            effectiveRange:nil];
if (tctopleft.y > frag.origin.y + .5*frag.size.height) {
    tctopleft.y += frag.size.height;
    ix = [self.tv.layoutManager glyphIndexForPoint:tctopleft
            inTextContainer:self.tv.textContainer
            fractionOfDistanceThroughGlyph:nil];
}
NSRange charRange =
    [self.tv.layoutManager characterRangeForGlyphRange:NSMakeRange(ix,0)
                                      actualGlyphRange:nil];
ix = charRange.location;

Finally, I’ll use NSLinguisticTagger to get the range of the entire word to which this character belongs:

NSLinguisticTagger* t =
    [[NSLinguisticTagger alloc]
     initWithTagSchemes:@[NSLinguisticTagSchemeTokenType] options:0];
t.string = self.tv.text;
NSRange r;
NSString* tag =
    [t tagAtIndex:ix scheme:NSLinguisticTagSchemeTokenType
        tokenRange:&r sentenceRange:nil];
if ([tag isEqualToString: NSLinguisticTagWord])
    NSLog(@"%@", [self.tv.text substringWithRange:r]);

Clearly, the same sort of technique could be used to formulate a custom response to a tap (“what word did the user just tap on?”).

By subclassing NSLayoutManager (and by implementing its delegate), many powerful effects can be achieved. As a simple example, I’ll carry on from the preceding code by drawing a rectangular outline around the word we just located. To make this possible, I have an NSLayoutManager subclass, MyLayoutManager, an instance of which is built into the Text Kit stack for this text view. MyLayoutManager has a public NSRange property, wordRange. Having worked out what word I want to outline, I set the layout manager’s wordRange and invalidate its drawing of that word, to force a redraw:

MyLayoutManager* lm = (MyLayoutManager*)self.tv.layoutManager;
lm.wordRange = r;
[lm invalidateDisplayForCharacterRange:r];

In MyLayoutManager, I’ve overridden the method that draws the background behind glyphs. At the moment this method is called, there is already a graphics context.

First, I call super. Then, if the range of glyphs to be drawn includes the glyphs for the range of characters in self.wordRange, I ask for the rect of the bounding box of those glyphs, and stroke it to form the rectangle. As I mentioned earlier, the bounding box is in text container coordinates, but now we’re drawing in the real world, so I have to compensate by offsetting the drawn rectangle by the same amount that the text container is supposed to be offset in the real world; fortunately, the text view tells us (origin) what that offset is:

-(void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow
        atPoint:(CGPoint)origin {
    [super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
    if (!self.wordRange.length)
        return;
    NSRange range =
        [self glyphRangeForCharacterRange:self.wordRange
            actualCharacterRange:nil];
    range = NSIntersectionRange(glyphsToShow, range);
    if (!range.length)
        return;
    NSTextContainer* tc =
        [self textContainerForGlyphAtIndex:range.location
            effectiveRange:nil];
    CGRect r = [self boundingRectForGlyphRange:range inTextContainer:tc];
    r.origin.x += origin.x;
    r.origin.y += origin.y;
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSaveGState(c);
    CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
    CGContextSetLineWidth(c, 1.0);
    CGContextStrokeRect(c, r);
    CGContextRestoreGState(c);
}

UITextView is the only built-in iOS class that has a Text Kit stack to which you are given programmatic access. But that doesn’t mean it’s the only place where you can draw with Text Kit! You can draw with Text Kit anywhere you can draw — that is, in any graphics context (Chapter 2). When you do so, you should always call both drawBackgroundForGlyphRange:atPoint: (the method I overrode in the previous example) and drawGlyphsForGlyphRange:atPoint:, in that order. The point: argument is where you consider the text container’s origin to be within the current graphics context.

To illustrate, I’ll change the implementation of the StringDrawer class that I described earlier in this chapter. Previously, StringDrawer’s drawRect: implementation told the attributed string (self.attributedText) to draw itself:

- (void)drawRect:(CGRect)rect {
    CGRect r = CGRectOffset(rect, 0, 2); // shoved down a little from top
    [self.attributedText drawWithRect:r
        options:NSStringDrawingTruncatesLastVisibleLine |
                NSStringDrawingUsesLineFragmentOrigin
        context:nil];
}

Instead, I’ll construct the Text Kit stack and tell its layout manager to draw the text:

- (void)drawRect:(CGRect)rect {
    NSLayoutManager* lm = [NSLayoutManager new];
    NSTextStorage* ts =
        [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
    [ts addLayoutManager:lm];
    NSTextContainer* tc =
        [[NSTextContainer alloc] initWithSize:rect.size];
    [lm addTextContainer:tc];
    tc.lineBreakMode = NSLineBreakByTruncatingTail;
    tc.lineFragmentPadding = 0;
    NSRange r = [lm glyphRangeForTextContainer:tc];
    [lm drawBackgroundForGlyphRange:r atPoint:CGPointMake(0,2)];
    [lm drawGlyphsForGlyphRange:r atPoint:CGPointMake(0,2)];
}

Building the entire Text Kit stack by hand may seem like overkill for that simple example, but imagine what else I could do now that I have access to the entire Text Kit stack! I can use properties, subclassing and delegation, and alternative stack architectures to achieve customizations and effects that, before iOS 7, were difficult or impossible to achieve without dipping down to the level of Core Text.

For example, the two-column display of U.S. state names on the iPad shown in Figure 10-14 was a Core Text example in previous editions of this book, requiring 50 or 60 lines of elaborate C code, complicated by the necessity of flipping the context to prevent the text from being drawn upside-down. But in iOS 7 it can be achieved easily through Objective-C and Text Kit — effectively just by reusing code from earlier examples in this chapter.

Furthermore, the example from previous editions went on to describe how to make the display of state names interactive, with the name of the tapped state briefly outlined with a rectangle (Figure 10-15). With Core Text, this was almost insanely difficult, not least because we had to keep track of all the line fragment rectangles ourselves. But it’s easy with Text Kit, because the layout manager knows all the answers.

We have a UIView subclass, StyledText. In its layoutSubviews, it creates the Text Kit stack — a layout manager with two text containers, to achieve the two-column layout — and stores the whole stack, along with the rects at which the two text containers are to be drawn, in properties:

-(void)layoutSubviews {
    [super layoutSubviews];
    CGRect r1 = self.bounds;
    r1.origin.y += 2; // a little top space
    r1.size.width /= 2.0; // column 1
    CGRect r2 = r1;
    r2.origin.x += r2.size.width; // column 2
    NSLayoutManager* lm = [MyLayoutManager new];
    NSTextStorage* ts =
        [[NSTextStorage alloc] initWithAttributedString:self.text];
    [ts addLayoutManager:lm];
    NSTextContainer* tc = [[NSTextContainer alloc] initWithSize:r1.size];
    [lm addTextContainer:tc];
    NSTextContainer* tc2 = [[NSTextContainer alloc] initWithSize:r2.size];
    [lm addTextContainer:tc2];
    self.lm = lm;
    self.ts = ts;
    self.tc = tc;
    self.tc2 = tc2;
    self.r1 = r1;
    self.r2 = r2;
}

Our drawRect: is just like the previous example, except that we have two text containers to draw:

- (void) drawRect:(CGRect)rect {
    NSRange range1 = [self.lm glyphRangeForTextContainer:self.tc];
    [self.lm drawBackgroundForGlyphRange:range1 atPoint:self.r1.origin];
    [self.lm drawGlyphsForGlyphRange:range1 atPoint:self.r1.origin];
    NSRange range2 = [self.lm glyphRangeForTextContainer:self.tc2];
    [self.lm drawBackgroundForGlyphRange:range2 atPoint:self.r2.origin];
    [self.lm drawGlyphsForGlyphRange:range2 atPoint:self.r2.origin];
}

So much for drawing the text! Now, when the user taps on our view, a tap gesture recognizer’s action handler is called. We are using the same layout manager subclass developed earlier in this chapter; it draws a rectangle around the glyphs corresponding to the characters of its wordRange property. Thus, all we have to do in order to make the flashing rectangle around the tapped word is work out what that range is, set the layout manager’s wordRange and redraw ourselves, and then set the layout manager’s wordRange back to a zero range and redraw ourselves again to remove the rectangle.

We start by working out which column the user tapped in; this tells us which text container it is, and what the tapped point is in text container coordinates:

CGPoint p = [g locationInView:self]; // g is the tap gesture recognizer
NSTextContainer* tc = self.tc;
if (!CGRectContainsPoint(self.r1, p)) {
    tc = self.tc2;
    p.x -= self.r1.size.width;
}

Now we can ask the layout manager what glyph the user tapped on, and hence the whole range of glyphs within the line fragment the user tapped in. If the user tapped to the left of the first glyph or to the right of the last glyph, no word was tapped, and we return:

CGFloat f;
NSUInteger ix =
    [self.lm glyphIndexForPoint:p inTextContainer:tc
        fractionOfDistanceThroughGlyph:&f];
NSRange glyphRange;
[self.lm lineFragmentRectForGlyphAtIndex:ix effectiveRange:&glyphRange];
if (ix == glyphRange.location && f == 0.0)
    return;
if (ix == glyphRange.location + glyphRange.length - 1 && f == 1.0)
    return;

If the last glyph of the line fragment is a whitespace glyph, we don’t want to include it in our rectangle, so we subtract it from the end of our range. Now we’re ready to convert to a character range, and thus we can learn the name of the state that the user tapped on:

while (NSGlyphPropertyControlCharacter &
    [self.lm propertyForGlyphAtIndex:
        glyphRange.location + glyphRange.length - 1])
    glyphRange.length -= 1;
NSRange characterRange =
    [self.lm characterRangeForGlyphRange:glyphRange actualGlyphRange:nil];
NSString* s = [self.text.string substringWithRange:characterRange];
NSLog(@"you tapped %@", s);

Finally, we flash the rectangle around the state name by setting and resetting the wordRange property of the subclassed layout manager:

MyLayoutManager* lm = (MyLayoutManager*)self.lm;
lm.wordRange = characterRange;
[self setNeedsDisplay];
dispatch_time_t popTime =
    dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
    lm.wordRange = NSMakeRange(0, 0);
    [self setNeedsDisplay];
});