Text Programming Guide For iOS
Text Programming Guide For iOS
Contents
Typographical Concepts 15
Characters and Glyphs 15 Typefaces and Fonts 17 Text Layout 17
Contents
Configuring the Keyboard for Web Views 39 Managing the Keyboard 40 Receiving Keyboard Notifications 40 Displaying the Keyboard 42 Dismissing the Keyboard 42 Moving Content That Is Located Under the Keyboard 42
Contents
Text Styles 69 Font Descriptors 70 Querying Font Metrics 72 Laying Out Text 73 The Layout Process 73 Generating Line Fragment Rectangles 74 Specifying Exclusion Paths 74 Specifying Multipage and Multicolumn Layouts 75
Typographical Concepts 15
Figure 2-1 Figure 2-2 Figure 2-3 Figure 2-4 Figure 2-5 Figure 2-6 Figure 2-7 Glyphs of the character A 15 Ligatures 16 Fonts in the Times font family 17 Glyph metrics 19 Kerning 19 Alignment of text relative to margins 20 Justified text 21
Figure 5-2 Figure 5-3 Figure 5-4 Listing 5-1 Listing 5-2 Listing 5-3
Several different keyboards and input methods 39 Relative keyboard sizes in portrait and landscape modes 41 Adjusting content to accommodate the keyboard 43 Handling the keyboard notifications 44 Additional methods for tracking the active text field. 45 Adjusting the frame of the content view and scrolling a field above the keyboard 46
Listing 10-1 Listing 10-2 Listing 10-3 Listing 10-4 Listing 10-5 Listing 10-6 Listing 10-7 Listing 10-8 Listing 10-9 Listing 10-10 Listing 10-11 Listing 10-12 Listing 10-13 Listing 10-14 Listing 10-15 Listing 10-16 Listing 10-17 Listing 10-18
Finding a substring using a regular expression 83 Implementing simple text entry 85 Declaring the IndexedPosition and IndexedRange classes 92 Implementing the IndexedPosition and IndexedRange classes 92 Inserting text input into storage and updating selected and marked ranges 94 Implementations of textInRange: and replaceRange:withText: 95 Returning ranges of selected and marked text 96 Setting the range of selected text and setting the marked text 96 Implementing positionFromPosition:offset: 98 Implementing offsetFromPosition:toPosition: 98 Implementing textRangeFromPosition:toPosition: 98 An implementation of firstRectForRange: 99 Mapping text range to enclosing rectangle 99 An implementation of closestPositionToPoint: 100 Mapping a point to a character index 100 Sending messages to the text input delegate 101 Spell-checking a document 102 Presenting a list of word completions for the current partial string 104
The iOS platform gives you many ways to display text in your apps and let users edit that text. It also lets you display formatted text and web content in your apps views. The resources at your disposal range from framework objectssuch as text views, text fields, and web viewsto text layout engines that you can use directly to draw, lay out, and otherwise manage text.
With the classes in the UIKit framework, you can manage the edit menu (including adding custom items to it), implement custom input views, and copy, cut, and paste data within and between apps. Note: This document was previously titled Text, Web, and Editing Programming Guide for iOS .
At a Glance
Apps in iOS have a number of powerful technologies to handle text, both for editing text and for rendering high-quality typographically formatted text.
The UIKit Framework Provides Your App with Text and Web Objects
You can add ready-made text views, text fields, and labels to your apps user interface by using instances of the UITextView, UITextField, and UILabel. You can add and configure them programmatically or by using the Interface Builder editor in Xcode. You can also turn a view of your app into a miniature web browser capable of understanding and displaying HTML, CSS, and JavaScript content. You do this using a UIWebView object. Relevant Chapters: Displaying Text Content in iOS (page 11), Typographical Concepts (page 15), Managing Text Fields and Text Views (page 22), Displaying Web Content (page 34)
When Users Edit Text, Your App Must Manage the Keyboard
When a user taps a text field, text view, or form field in a web view, iOS animates a keyboard into view. An app can control which keyboard is presented; for example, for a numeric-value field, the app should select the numeric keypad. If the entered or edited text is obscured by the keyboard, the app should adjust the view displaying the text so that the text appears above the keyboard. The delegate of a text view, text field, or web view is responsible for validating edited text and for accessing and storing edited text when the user dismisses the keyboard. Relevant Chapters: Managing the Keyboard (page 37)
Relevant Chapter: Using Text Kit to Draw and Manage Text (page 64), Lower Level Text-Handling Technologies (page 79)
Your App Has a Range of Options for the Input and Editing of Data
The UIKit framework includes programmatic interfaces for editing the data in a view and for entering data into an app. Custom input views can replace the system keyboard to permit input of special data; input accessory views are a custom view above the system keyboard (or custom input view) that enables users to affect edited data in app-specific ways. Using UIPasteboard and related classes, an app can copy, cut, and paste data within different locations of itself or between itself and another app. As part of copy-cut-paste operations, the user taps a command on an contextual edit menu; your app manages this menu and can add custom commands to it. Relevant Chapters:: Copy, Cut, and Paste Operations (page 47), Displaying and Managing the Edit Menu (page 59), Custom Views for Data Input (page 55)
See Also
The Core Graphics and Core Animation frameworks have some text-handling capabilities. Core Animation, for example, offers the CATextLayer class. To learn more about these capabilities, read Quartz 2D Programming Guide (Core Graphics) and Core Animation Programming Guide . To find out more about the Core Text framework, which is apropriate for developing higher-level text-handling frameworks, read Core Text Programming Guide and Core Text Reference Collection .
10
The text system in iOS provides a tremendous amount of power while still being very simple to use. The UIKit framework includes several high-level classes for managing the display and input of text. UIKit also includes a class for displaying HTML, CSS, and JavaScript-based web content.
UILabel defines a label, which displays a static text string. UITextField defines a text field, which displays a single line of editable text. UITextView defines a text view, which displays multiple lines of editable text.
Although these classes actually can support the display of arbitrary amounts of text, labels and text fields are intended to be used for relatively small amounts of text, typically a single line. Text views, on the other hand, are meant to display large amounts of text. Text view objects, created from the UITextView class, display text formatted into paragraphs, columns, and pages, with all the characteristics of fine typesetting, such as kerning, ligatures, sophisticated line-breaking, and justification. These typographic services are supplied to UITextView through an underlying technology called Text Kit, a powerful layout engine that is both easy to use and extensible. See Using Text Kit to Draw and Manage Text (page 64) for more information about Text Kit.
11
Displaying Text Content in iOS Use Text Objects to Display Text Content
Figure 1-1 shows examples of the primary text objects as they appear on screen. The image on the left shows several different styles of text fields while the image on the right shows a single text view. The callouts displayed on the background are UILabel objects embedded inside the table cells used to display the different views. (These examples were taken from the UICatalog sample app, which demonstrates many of the views and controls available in UIKit.)
Figure 1-1 Text classes in the UICatalog app
When working with editable text fields and text views, you should always provide a delegate object to manage the editing session. Text views send several different notifications to the delegate to let them know when editing begins, when it ends, and to give them a chance to override some editing actions. For example, the delegate can decide if the current text contains a valid value and prevent the editing session from ending if it does not. When editing does finally end, you also use the delegate to get the resulting text value and update your apps data model. Because there are slight differences in their intended usage, the delegate methods for each text view are slightly different. A delegate that supports the UITextField class implements the methods of the UITextFieldDelegate protocol. Similarly, a delegate that supports the UITextView class implements the methods of the UITextViewDelegate protocol. In both cases, you are not required to implement any of the protocol methods, but if you do not, the text field or view is not as useful.
12
Displaying Text Content in iOS Use Web Views to Display Web Content
Managing Text Fields and Text Views (page 22) describes the sequence of delegation messages for both text fields and text views and discusses various tasks performed by the delegates of these objects. For more information about the methods of the UITextFieldDelegate and UITextViewDelegate protocols, see UITextFieldDelegate Protocol Reference and UITextViewDelegate Protocol Reference .
13
Displaying Text Content in iOS Use Web Views to Display Web Content
Figure 1-2 shows an example of a UIWebView object from the UICatalog sample app, which demonstrates many of the views and controls available in UIKit. Because it just displays HTML content, if you want the user to be able to navigate pages much like they would in a web browser, you need to add controls to do so.
Figure 1-2 A web view
A web view provides information about when pages are loaded, and whether there were any load errors, through its associated delegate object. A web delegate is an object that implements one or more methods of the UIWebViewDelegate protocol. Your implementations of the delegate methods can respond to failures or perform other tasks related to the loading of a web page. Displaying Web Content (page 34) describes how to display HTML and other content in a web view.
14
Typographical Concepts
This chapter defines some important typographical concepts relevant to the text system. Many of the terms representing these concepts are reflected in text system APIs. If youre familiar with typography, you can skip this chapter.
15
Characters and glyphs do not have a one-to-one correspondence. In some cases a character may be represented by multiple glyphs, such as an which may be an e glyph combined with an acute accent glyph . In other cases, a single glyph may represent multiple characters, as in the case of a ligature, or joined letter. Figure 2-2 shows individual characters and the single-glyph ligature often used when they are adjacent.
Figure 2-2 Ligatures
A ligature is an example of a contextual form in which the glyph used to represent a character changes depending on the characters next to it. Other contextual forms include alternate glyphs for characters beginning or ending a word. Computers store characters as numbers mapped by encoding tables to their corresponding characters. The encoding scheme native to iOS and OS X conforms to the Unicode standard. Unicode provides a standard methodology for assigning a unique number for every character in every modern written language in the world, independent of the platform, program, and programming language being used. This universal standard solves a longstanding problem of different computer systems using hundreds of conflicting encoding schemes. It also provides information specifying how to handle bidirectional text and contextual forms; how to form words and break lines; how to sort text in different languages; and how to format numbers, dates, times, and other elements appropriate to different languages. Glyphs are also represented by numeric codes called glyph codes. The glyphs used to depict characters are selected by the layout manager during composition and layout processing. The layout manager determines which glyphs to use and where to place them in the display, or view. The layout manager caches the glyph codes in use and provides methods to convert between characters and glyphs and between characters and view coordinates.
16
Styles, also called traits , include variations such as bold, italic, condensed, expanded, narrow, small caps, poster fonts, and fixed pitch. The text system includes objects called font descriptors, which provide font-matching capability, so that you can partially describe a font by creating a font descriptor with, for example, just a family name or weight, and you can then find all the fonts on the system that match the given trait.
Text Layout
Text layout is the process of arranging glyphs on a display device, in an area called a text view , which represents an area similar to a page in traditional typesetting. The order in which glyphs are laid out relative to each other is called text direction. In English and other languages derived from Latin, glyphs are placed side by side to
17
form words that are separated by spaces. Words are laid out in lines beginning at the top left of the text view proceeding from left to right until the text reaches the right side of the view. Text then begins a new line at the left side of the view under the beginning of the previous line, and layout proceeds in the same manner to the bottom of the text view. In other languages, glyph layout can be quite different. For example, some languages lay out glyphs from right to left or vertically instead of horizontally. It is common, especially in technical writing, to mix languages with differing text direction, such as English and Hebrew, in the same line. Some writing systems even alternate layout direction in every other line (an arrangement called boustrophedonic writing). Some languages do not group glyphs into words separated by spaces. Moreover, some apps call for arbitrary arrangements of glyphs; for example, in a graphic design context, a layout may require glyphs to be arranged on a nonlinear path. To create lines from a string of glyphs, the layout engine must perform line breaking by finding a point at which to end one line and begin the next. In the text system, you can specify line breaking at either word or glyph boundaries. In roman text, a word broken between glyphs requires insertion of a hyphen glyph at the breakpoint. The layout manager lays out glyphs along an invisible line called the baseline. In roman text, the baseline is horizontal, and the bottom edge of most of the glyphs rest on it. Some glyphs extend below the baseline, including those for characters like g that have descenders, or tails, and large rounded characters like O that must extend slightly below the baseline to compensate for optical effects. Other writing systems place glyphs below or centered on the baseline. Every glyph includes an origin point that the layout manager uses to align it properly with the baseline. Glyph designers provide a set of measurements with a font, called metrics, which describe the spacing around each glyph in the font. The layout manager uses these metrics to determining glyph placement. In horizontal text, the glyph has a metric called the advance width, which measures the distance along the baseline to the origin point of the next glyph. Typically there is some space between the origin point and the left side of the glyph, which is called the left-side bearing. There may also be space between the right side of the glyph and the point described by the advance width, which is called the right-side bearing. The vertical dimension of the glyph is provided by two metrics called the ascent and the descent. The ascent is the distance from the
18
origin (on the baseline) to the top of the tallest glyphs in the font. The descent, which is the distance below the baseline to the bottom of the fonts deepest descender. The rectangle enclosing the visible parts of the glyph is called the glyphterms.eps bounding rectangle or bounding box. Figure 2-4 illustrates these metrics.
Figure 2-4 Glyph metrics
Cocoa_Text_Architecture Apple, Inc.
By default, in horizontal text, typesetters place glyphs side-by-side using the advance width, resulting in a standard interglyph space. However, in some combinations, text is made more readable by kerning, which is shrinking or stretching the space between two glyphs. A very common example of kerning occurs between an uppercase W and uppercase A, as shown in Figure 2-5. Type designers include kerning information in the metrics for a font. The text system provides methods to turn kerning off, use the default settings provided with the font, or tighten or loosen the kerning throughout a selection of text. kerning.eps
Cocoa_Text_Architecture Apple, Inc.
Figure 2-5
Kerning
19
Type systems usually measure font metrics in units called points, which measure exactly 72 per inch in most computer typesetting systems. Adding the distance of the ascent and the descent of a font provides the fonts point size. Space added during typesetting between lines of type is called leading, after the slugs of lead used for that purpose in traditional metal-type page layout. The total amount of ascent plus descent plus leading provides a fonts line height. (Leading is sometimes also called linegap . It is often specified as a ratio of a fonts point size over the line height at which a block of text is set, such as 14/16.5.) Although the preceding typographic concepts of type design may be somewhat esoteric, most people who have created documents on a computer or typewriter are familiar with the elements of text layout on a page. For example, the margins are the areas of white space between the edges of the page and the text area where the layout engine places glyphs. Alignment describes the way text lines are placed relative to the margins. For example, horizontal text can be aligned right, left, or centered, as shown in Figure 2-6.
Figure 2-6 Alignment of text relative to margins
20
Lines of text can also be justified; for horizontal text the lines are aligned on both right and left margin by varying interword and interglyph spacing, as shown in Figure 2-7. The system performs alignment and justification, if requested, after the text stream has been broken into lines and hyphens added and other glyph substitutions made. justified.eps
Figure 2-7 Justified text
Cocoa_Text_Architecture Apple, Inc.
21
Text fields and text views have two main functions: to display text and to enable the entry and editing of text. Several programming tasks are associated with these simple purposes, including configuring the text object, accessing the current text, validating what the user enters, and displaying overlay views such as bookmark buttons in text fields. The delegate of a UITextField or UITextView object is responsible for most of these tasks. The delegate must adopt the UITextFieldDelegate or UITextViewDelegate protocols and implement one or more of the protocol methods. Implementation of all protocol methods is optional. To have these methods called, you must set the delegate properties of text fields and text views either programmatically or in Interface Builder.
Just before a text object becomes first respondertextFieldShouldBeginEditing: (text field) and textViewShouldBeginEditing: (text view).
22
Managing Text Fields and Text Views The Sequence of Messages to the Delegate
The delegate can verify whether the text object should become first responder by returning YES (the default) or NO.
2.
Just after a text object becomes first respondertextFieldDidBeginEditing: (text field) and textViewDidBeginEditing: (text view). The delegate can respond to this message by updating state information or, for example, by showing an overlay view during the editing session.
3.
During the editing sessionvarious. While the user enters and edits text, the text object invokes certain delegation methods (if implemented). For example, the delegate of a text view can receive a textViewDidChange: message when any text changes. The delegate of a text field can receive a textFieldShouldClear: message when the user taps the clear button of a text field; the delegate returns a Boolean value indicating whether the text should be cleared.
4.
Just before a text object resigns first respondertextFieldShouldEndEditing: (text field) and textViewShouldEndEditing: (text view). The primary reason for a delegate to implement these methods is to validate entered text. For example, if text should conform to a given format, the delegate validates the entered string here and returns NO if the string does not conform. The default return value is YES. A related method for text fields is textFieldShouldReturn:. When the user taps the return key, the text field class sends a textFieldShouldReturn: message to the delegate to ask whether it should resign first responder.
5.
Just after text a object resigns first respondertextFieldDidEndEditing: (text field) and textViewDidEndEditing: (text view). A delegate can implement these methods to get the text that the user has just entered or edited.
Objects other than the delegate can be informed of changes in the first-responder status of text views and text fields by observing notifications. (They cant, however, approve or deny the transition to a new status.) The notifications have names such as UITextFieldTextDidBeginEditingNotification, UITextViewTextDidEndEditingNotification, and UITextViewTextDidChangeNotification. As with textFieldDidEndEditing: and textViewDidEndEditing:, the primary reason for observing and handling the UITextFieldTextDidEndEditingNotification and UITextViewTextDidEndEditingNotification notifications is to access the text in the associated text field or text view. See UITextField Class Reference and UITextView Class Reference to learn more about the notifications posted by these classes.
23
Managing Text Fields and Text Views Configuring Text Fields and Text Views
Text characteristicsText color, alignment, font family, font typeface, and font size. KeyboardKeyboard type, return key name, secure text entry, and auto-enabled return key, all of which are declared by the UITextInputTraits protocol. (Note that an auto-enabled return key associated with a text view acts as a carriage-return key when tapped.) For more information, see Configuring the Keyboard for Text Objects (page 37). Text-field specificBorder, background image, disabled image, clear button, and placeholder text. As a UIControl object, text fields also have highlighted, selected, enabled, and other properties. Text-view specificEditable status, data detectors (for phone numbers and URL links). Because a text view inherits from UIScrollView, you can also manage scroll-view behavior by setting the appropriate properties.
24
Managing Text Fields and Text Views Tracking Multiple Text Fields or Text Views
Defining outlet connections for the text objects in a view is especially useful, even essential, when you need to write string values to these objects, not just obtain them. For the tag approach, declare a set of enum constants, one constant for each tag.
enum { NameFieldTag = 0, EmailFieldTag, DOBFieldTag, SSNFieldTag };
Then assign the integer value to the tag property of the text object, either programmatically or in the attribute inspector of Interface Builder. (The tag property is declared by UIView.) In a delegation method, you can use a switch statement to evaluate the tag value of the passed-in text object and proceed accordingly (as shown in Listing 3-2).
Listing 3-2 Identifying the passed-in text object using tags
- (void)textFieldDidEndEditing:(UITextField *)textField {
switch (textField.tag) { case NameFieldTag: // do something with this text field break; case EmailFieldTag: // do something with this text field break; // remainder of switch statement.... } }
25
Managing Text Fields and Text Views Getting the Entered Text and Setting Text
switch (textField.tag) { case NameFieldTag: [thePerson setObject:textField.text forKey:MyAppPersonNameKey]; break; case EmailFieldTag: [thePerson setObject:textField.text forKey:MyAppPersonEmailKey]; break; case SSNFieldTag: [thePerson setObject:textField.text forKey:MyAppPersonSSNKey]; break; default: break; } }
Listing 3-4 shows an implementation of the textViewDidEndEditing: method that gets the displayed string from the text view and stores it in a dictionary. Here the method doesnt ask the text view to resign first responder. (The resignFirstResponder method was called earlier in an action method invoked when the user tapped a Done button in the views user interface.)
26
Managing Text Fields and Text Views Using Formatters with Text Fields
Listing 3-4
- (void)textViewDidEndEditing:(UITextView *)textView { NSString *theText = textView.text; if (![theText isEqualToString:@""]) { [thePerson setObject:theText forKey:MyAppPersonNotesKey]; } doneButton.enabled = NO; }
If you need to write string values to text objectsusually after retrieving them from the apps data modelsimply assign the strings to the text property of the text object. For example:
NSString *storedValue = [thePerson objectForKey:MyAppPersonEmailKey]; emailField.text = storedValue;
To do this, its useful to define outlets for each text field or text view that you want to write string values to (emailField, in this example).
And your app can use formatter objects to convert the strings into an NSDate object and an NSNumber object, respectively. The following code listings use a date-formatter object to illustrate the use of formatters. (Of course, you could use a UIDatePicker object for date input rather than a text field, but a text field with an attached date formatter is another option.) The code in Listing 3-5 creates an NSDateFormatter object and assigns it to an
27
Managing Text Fields and Text Views Using Formatters with Text Fields
instance variable. It configures the date formatter to use the short style for dates, but in a way that is responsive to changes in calendar, locale, and time zone. It also assigns todays date in the given format as a placeholder string so that users have a model to follow when they enter dates.
Listing 3-5 Configuring a date formatter
- (void)viewDidLoad { [super viewDidLoad]; dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setGeneratesCalendarDates:YES]; [dateFormatter setLocale:[NSLocale currentLocale]]; [dateFormatter setCalendar:[NSCalendar autoupdatingCurrentCalendar]]; [dateFormatter setTimeZone:[NSTimeZone defaultTimeZone]]; [dateFormatter setDateStyle:NSDateFormatterShortStyle]; // example: 4/13/10 DOB.placeholder = [NSString stringWithFormat:@"Example: %@", [dateFormatter stringFromDate:[NSDate date]]];
// code continues.... }
After you have configured the date formatter, the delegate can call the dateFromString: method on the formatter to convert the entered date string into an NSDate object, as shown in Listing 3-6.
Listing 3-6 Using an NSDateFormatter object to convert a date string to a date object
- (void)textFieldDidEndEditing:(UITextField *)textField { [textField resignFirstResponder]; if ([textField.text isEqualToString:@""]) return; switch (textField.tag) { case DOBField: NSDate *theDate = [dateFormatter dateFromString:textField.text];; if (theDate) [inputData setObject:theDate forKey:MyAppPersonDOBKey]; break; // more switch case code here... default:
28
break; } }
The use of formatters does not guarantee that the entered string contains valid valuesfor example, a user could enter 13 for a month number in the Gregorian calendar. To ensure that the user has entered a correct value, the delegate must validate the string as explained in Validating Entered Text (page 29). And because validation often requires a known format and range of valid values, if you configure the date formatter as in Listing 3-5 so that it is sensitive to different calendars and locales, the format cannot be known with certainty. To specify a known date format, configure the date formatter by calling setDateFormat:, passing in a format pattern defined by the Unicode standard. You can also reverse the procedure shown above: Convert a date object to a string in a given format by calling the NSDateFormatter method stringFromDate: and then assign that string to the text property of a text field, text view, or label. For more information on NSDateFormatter and NSNumberFormatter, see Data Formatting Guide .
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField { if (textField == SSN) { // SSN is an outlet NSString *regEx = @"[0-9]{3}-[0-9]{2}-[0-9]{4}"; NSRange r = [textField.text rangeOfString:regEx options:NSRegularExpressionSearch]; if (r.location == NSNotFound) {
29
UIAlertView *av = [[[UIAlertView alloc] initWithTitle:@"Entry Error" message:@"Enter social security number in 'NNN-NN-NNNN' format" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease]; [av show]; return NO; } } return YES; }
The implementation of textViewShouldEndEditing: in Listing 3-8 enforces a character limit for the text entered in a text view.
Listing 3-8 Validating a text views string for allowable length
- (BOOL)textViewShouldEndEditing:(UITextView *)textView { if (textView.text.length > 50) { UIAlertView *av = [[[UIAlertView alloc] initWithTitle:@"Entry Error" message:@"You must enter less than 50 characters." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Clear", nil] autorelease]; [av show]; return NO; } return YES; }
The delegate can also validate each character as it is entered into a text field by implementing the textField:shouldChangeCharactersInRange:replacementString: method. The code in Listing 3-9 verifies that each entered character (string) represents a digit. (You could accomplish the same goal by specifying a UIKeyboardTypeNumberPad keyboard for the text field.)
Listing 3-9 Validating each character as its entered
30
Managing Text Fields and Text Views Using Overlay Views in Text Fields
replacementString:(NSString *)string { if ([string isEqualToString:@""]) return YES; if (textField.tag == SalaryFieldTag) { unichar c = [string characterAtIndex:0]; if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:c]) { return YES; } else { return NO; } }
return YES; }
You can also implement the textField:shouldChangeCharactersInRange:replacementString: method to offer possible word completions or corrections to the user as they enter text.
To implement an overlay view, create a view of a size that fits within the height of the text field and give the view an appropriately sized image. If the view is a button or other control, specify a target object, an action selector, and the triggering control events. Usually you want an overlay view to appear when its text field is the focus of editing, so assign it to the text fields leftView or rightView property in the delegates textFieldDidBeginEditing: method. You can control when an overlay view appears during the editing sessionfor example, before the user begins entering text or only after the user begins entering textby assigning a UITextFieldViewMode constant to the leftViewMode or rightViewMode property. Listing 3-10 illustrates how you might implement an overlay view.
31
Managing Text Fields and Text Views Using Overlay Views in Text Fields
@dynamic overlayButton;
- (UIButton *)overlayButton { if (!overlayButton) { overlayButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; UIImage *overlayImage = [UIImage imageNamed:@"bookmark.png"]; if (overlayImage) { [overlayButton setImage:overlayImage forState:UIControlStateNormal]; [overlayButton addTarget:self action:@selector(bookmarkTapped:) forControlEvents:UIControlEventTouchUpInside]; } } return overlayButton; }
If you use a control for an overlay view, be sure to implement the action method. To remove an overlay view, simply set the leftView or rightView property to nil in the textFieldDidEndEditing: delegation method, as in Listing 3-11.
Listing 3-11 Removing the overlay view
- (void)textFieldDidEndEditing:(UITextField *)textField {
32
Managing Text Fields and Text Views Tracking the Selection in Text Views
33
If your user interface includes a UIWebView object, you can display local content or content that is loaded from the network.
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"iPhone_User_Guide" ofType:@"pdf"]; if (thePath) { NSData *pdfData = [NSData dataWithContentsOfFile:thePath]; [(UIWebView *)self.view loadData:pdfData MIMEType:@"application/pdf" textEncodingName:@"utf-8" baseURL:nil]; } }
The text encoding string has no effect on PDF data but is retained in the listing for example purposes.
34
Because loading a web resource might take some time, you might display an activity indicator to indicate that the load is underway. You can do this by assigning a delegate to the web view and implementing the UIWebViewDelegate methods, as in Listing 4-2. The delegate displays an activity indicator when the load starts and hides it when the load ends. If there is a problem with the load, it creates an HTML error message and, using the loadHTMLString:baseURL: method, loads it into the web view for display.
Listing 4-2 The web-view delegate managing network loading
- (void)webViewDidStartLoad:(UIWebView *)webView { // starting the load, show the activity indicator in the status bar [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; }
- (void)webViewDidFinishLoad:(UIWebView *)webView { // finished loading, hide the activity indicator in the status bar [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; }
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { // load error, hide the activity indicator in the status bar [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// report the error inside the webview NSString* errorString = [NSString stringWithFormat: @"<html><center><font size=+5 color='red'> An error occurred:<br>%@</font></center></html>", error.localizedDescription]; [self.myWebView loadHTMLString:errorString baseURL:nil]; }
35
If, after initiating a network-based load request, you must release your web view for any reason, you must cancel the pending request before releasing the web view. You can cancel a load request using the web views stopLoading method. A typical place to include this code would be in the viewWillDisappear: method of the owning view controller. To determine if a request is still pending, you can check the value in the web views loading property. Listing 4-3 illustrates how you might do this.
Listing 4-3 Stopping a load request when the web view is to disappear
- (void)viewWillDisappear:(BOOL)animated { if ( [self.myWebView loading] ) { [self.myWebView stopLoading]; } self.myWebView.delegate = nil; is hidden // disconnect the delegate as the webview
The loadRequest: example is taken from the UICatalog sample code project.
36
When users touch a text field, a text view, or a field in a web view, the system displays a keyboard. You can configure the type of keyboard that is displayed along with several attributes of the keyboard. You also have to manage the keyboard when the editing session begins and ends. Because the keyboard could hide the portion of your view that is the focus of editing, this management might include adjusting the user interface to raise the area of focus so that is visible above the keyboard.
37
offer similar features as the default keyboard but provide additional buttons that are specially suited to particular tasks. However, the phone and numerical keyboards offer a dramatically different layout that is tailored towards numerical input.
Figure 5-1 Several different keyboard types
38
To implement the language preferences of different users, iOS also supports different input methods and keyboard layouts for different languages, some of which are shown in Figure 5-2. The input method and layout for the keyboard is determined by the users language preferences. Input for some of these keyboards takes place in keyboard_input.eps multiple stages.
Figure
Text, Web, and Editing Programming Guide for iOS Apple, Inc. 5-2 Several different keyboards and input methods keyboard_input.tif reduced to 20%
39
You can also control which type of keyboard is displayed when a user touches a text field in a web page. To display a telephone keypad, an email keyboard, or a URL keyboard, use the tel, email, or url keywords for the type attribute on an input element, respectively. To display a numeric keyboard, set the value of the pattern attribute to "[0-9]*" or "\d*". These keywords and the pattern attribute are part of HTML 5 and are available in iOS. The following list shows how to display each type of keyboard, including the standard keyboard. Text: <input type="text"></input> Telephone: <input type="tel"></input> URL: <input type="url"></input> Email: <input type="email"></input> Zip code: <input type="text" pattern="[0-9]*"></input>
Each keyboard notification includes information about the size and position of the keyboard on the screen. You can access this information from the userInfo dictionary of each notification using the UIKeyboardFrameBeginUserInfoKey and UIKeyboardFrameEndUserInfoKey keys; the former gives the beginning keyboard frame, the latter the ending keyboard frame (both in screen coordinates). You should always use the information in these notifications as opposed to assuming the keyboard is a particular size or
40
in a particular location. The size of the keyboard is not guaranteed to be the same from one input method to another and may also change between different releases of iOS. In addition, even for a single language and system release, the keyboard dimensions can vary depending on the orientation of your app. For example, Figure 5-3 shows the relative sizes of the URL keyboard in both the portrait and landscape modes. Using the information inside the keyboard notifications ensures that you always have the correct size and position information.
Figure 5-3 Relative keyboard sizes in portrait and landscape modes
Note: The rectangle contained in the UIKeyboardFrameBeginUserInfoKey and UIKeyboardFrameEndUserInfoKey properties of the userInfo dictionary should be used only for the size information it contains. Do not use the origin of the rectangle (which is always {0.0, 0.0}) in rectangle-intersection operations. Because the keyboard is animated into position, the actual bounding rectangle of the keyboard changes over time.
One reason to use keyboard notifications is so that you can reposition content that is obscured by the keyboard when it is visible. For information on how to handle this scenario, see Moving Content That Is Located Under the Keyboard (page 42). There is no defined relationship between the timing of keyboard notifications and the timing of view-controller transitions.
41
Everything from that point on is handled for you automatically by the text object.
42
Adjusting your content typically involves temporarily resizing one or more views and positioning them so that the text object remains visible. The simplest way to manage text objects with the keyboard is to embed them inside a UIScrollView object (or one of its subclasses like UITableView). When the keyboard is displayed, all you have to do is reset the content area of the scroll view and scroll the desired text object into position. Thus, in response to a UIKeyboardDidShowNotification, your handler method would do the following:
1. 2. 3.
Get the size of the keyboard. Adjust the bottom content inset of your scroll view by the keyboard height. Scroll the target text field into view.
Note: The UITableViewController class automatically resizes and repositions its table view when there is in-line editing of text fields. See View Controllers and Navigation-Based Apps in Table View Programming Guide for iOS .
Figure 5-4 illustrates the preceding steps for a simple app that embeds several text fields inside a UIScrollView object. When the keyboard appears, the notification handler method adjusts the content and scroll indicator insets of the scroll view and then uses the scrollRectToVisible:animated: method of UIScrollView to scroll the tapped text field (in this case the email field) into view.
Figure 5-4 Adjusting content to accommodate the keyboard
43
Listing 5-1 shows the code for registering to receive keyboard notifications and shows the handler methods for those notifications. This code is implemented by the view controller that manages the scroll view, and the scrollView variable is an outlet that points to the scroll view object. The keyboardWasShown: method gets the keyboard size from the info dictionary of the notification and adjusts the bottom content inset of the scroll view by the height of the keyboard. It also sets the scrollIndicatorInsets property of the scroll view to the same value so that the scrolling indicator wont be hidden by the keyboard. Note that the keyboardWillBeHidden: method doesnt use the keyboard size; it simply sets the scroll views contentInset and scrollIndicatorInsets properties to the default value, UIEdgeInsetsZero. If the active text field is hidden by the keyboard, the keyboardWasShown: method adjusts the content offset of the scroll view appropriately. The active field is stored in a custom variable (called activeField in this example) that is a member variable of the view controller and set in the textFieldDidBeginEditing: delegate method, which is itself shown in Listing 5-2 (page 45). (In this example, the view controller also acts as the delegate for each of the text fields.)
Listing 5-1 Handling the keyboard notifications
// Call this method somewhere in your view controller setup code. - (void)registerForKeyboardNotifications { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil];
// Called when the UIKeyboardDidShowNotification is sent. - (void)keyboardWasShown:(NSNotification*)aNotification { NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
44
// If active text field is hidden by keyboard, scroll it so it's visible // Your app might not need or want this behavior. CGRect aRect = self.view.frame; aRect.size.height -= kbSize.height; if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) { [self.scrollView scrollRectToVisible:activeField.frame animated:YES]; } }
// Called when the UIKeyboardWillHideNotification is sent - (void)keyboardWillBeHidden:(NSNotification*)aNotification { UIEdgeInsets contentInsets = UIEdgeInsetsZero; scrollView.contentInset = contentInsets; scrollView.scrollIndicatorInsets = contentInsets; }
Listing 5-2 shows some additional code used by the view controller to set and clear the activeField variable in the preceding example. During initialization, each text field in the interface sets the view controller as its delegate. Therefore, when a text field becomes active, it calls these methods. For more information on text fields and their delegate notifications, see Managing Text Fields and Text Views (page 22).
Listing 5-2 Additional methods for tracking the active text field.
45
There are other ways you can scroll the edited area in a scroll view above an obscuring keyboard. Instead of altering the bottom content inset of the scroll view, you can extend the height of the content view by the height of the keyboard and then scroll the edited text object into view. Although the UIScrollView class has a contentSize property that you can set for this purpose, you can also adjust the frame of the content view, as shown in Listing 5-3. This code also uses the setContentOffset:animated: method to scroll the edited field into view, in this case scrolling it just above the top of the keyboard.
Listing 5-3 Adjusting the frame of the content view and scrolling a field above the keyboard
- (void)keyboardWasShown:(NSNotification*)aNotification { NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; CGRect bkgndRect = activeField.superview.frame; bkgndRect.size.height += kbSize.height; [activeField.superview setFrame:bkgndRect]; [scrollView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height) animated:YES]; }
46
Users can copy text, images, or other data in one app and paste that data to another location within the same app or in a different app. You can, for example, copy a persons address in an email message and paste it into the appropriate field in the Contacts app. The UIKit framework implements copy-cut-paste in the UITextView, UITextField, and UIWebView classes. If you want this behavior in your own apps, you can either use objects of these classes or implement copy-cut-paste yourself. The following sections describe the programmatic interfaces of the UIKit that you use for copy, cut, and paste operations and explain how they are used. Note: For usage guidelines related to copy and paste operations, see Supporting Copy and Paste in iOS Human Interface Guidelines .
The UIPasteboard class provides pasteboards: protected areas for sharing data within an app or between apps. The class offers methods for writing and reading items of data to and from a pasteboard. The UIMenuController class displays an edit menu above or below the selection to be copied, cut, or pasted into. The default commands of the edit menu are (potentially) Copy, Cut, Paste, Select, and Select All. You can also add custom menu items to the edit menu (see Adding Custom Items to the Edit Menu (page 62)). The UIResponder class declares the method canPerformAction:withSender:. Responder classes can implement this method to show and remove commands of the edit menu based on the current context. The UIResponderStandardEditActions informal protocol declares the interface for handling copy, cut, paste, select, and select-all commands. When users tap one of the commands in the edit menu, the corresponding UIResponderStandardEditActions method is invoked.
47
Pasteboard Concepts
A pasteboard is a standardized mechanism for exchanging data within apps or between apps. The most familiar use for pasteboards is handling copy, cut, and paste operations:
When a user selects data in an app and chooses the Copy (or Cut) menu command, the selected data is placed onto a pasteboard. When the user chooses the Paste menu command (either in the same or a different app), the data on a pasteboard is copied to the current app from the pasteboard.
In iOS, a pasteboard is also used to support Find operations. Additionally, you may use pasteboards to transfer data between apps using custom URL schemes instead of copy, cut, and paste commands; see Updating Your Info.plist Settings in iOS App Programming Guide for information about this technique. Regardless of the operation, the basic tasks you perform with a pasteboard object are to write data to a pasteboard and to read data from a pasteboard. Although these tasks are conceptually simple, they mask a number of important details. The main complexity is that there may be a number of ways to represent data, and this complexity leads to considerations of efficiency. These and other issues are discussed in the following sections.
Named Pasteboards
Pasteboards may be public or private. Public pasteboards are called system pasteboards; private pasteboards are created by apps, and hence are called app pasteboards. Pasteboards must have unique names. UIPasteboard defines two system pasteboards, each with its own name and purpose:
UIPasteboardNameGeneral is for cut, copy, and paste operations involving a wide range of data types.
You can obtain a singleton object representing the General pasteboard by invoking the generalPasteboard class method.
UIPasteboardNameFind is for search operations. The string currently typed by the user in the search
bar (UISearchBar) is written to this pasteboard, and thus can be shared between apps. You can obtain an object representing the Find pasteboard by calling the pasteboardWithName:create: class method, passing in UIPasteboardNameFind for the name. Typically you use one of the system-defined pasteboards, but if necessary you can create your own app pasteboard using pasteboardWithName:create: If you invoke pasteboardWithUniqueName, UIPasteboard gives you a uniquely-named app pasteboard. You can discover the name of a pasteboard through its name property.
48
Pasteboard Persistence
Pasteboards can be persistent. When a pasteboard is persistent, it continues to exist past app terminations and across system reboots. System pasteboards are persistent. Although app pasteboards by default are not persistent, an app can mark them as persistent by setting the persistent property to YES. App pasteboards that are not persistent only last until the owning (creating) app quits. A persistent app pasteboard is removed when the app that created it is uninstalled.
49
For example, suppose an app supported selection of rich text and images. It may want to place on a pasteboard both rich text and Unicode versions of a text selection and different representations of an image selection. Each representation of each item is stored with its own data, as shown in Figure 6-1.
Figure 6-1 Pasteboard items and representations
In general, to maximize the potential for sharing, pasteboard items should include as many different representations as possible. A pasteboard reader must find the data type that best suits its capabilities (if any). Typically, this means selecting the richest type available. For example, a text editor might provide HTML (rich text) and plain-text representations of copied text data. An app that supports rich text should retrieve the HTML representation and an app that only supports plain text should retrieve the plain-text version.
50
Copy, Cut, and Paste Operations First Steps: Identify the Selection and Display the Edit Menu
Change Count
The change count is a per-pasteboard variable that increments every time the contents of the pasteboard changesspecifically, when items are added, modified, or removed. By examining the change count (through the changeCount property), an app can determine whether the current data in the pasteboard is the same as the data it last received. Every time the change count is incremented, the pasteboard sends a notification to interested observers.
First Steps: Identify the Selection and Display the Edit Menu
If you are going to copy, cut, or paste something, you first must select it. (A paste operation often operates on an empty selection such a caret, indicating a position within a collection of items.) After selecting an itemand visually indicating the selectionyou should display the edit menu. The edit menu is a system menu that can potentially have the following commands in it: Copy, Cut, Paste, Select, and Select All. The edit menu points at the selection. When the user taps a menu item, the appropriate UIResponderStandardEditActions method implementation (such as cut: or paste:) is invoked. For more about selections and to learn how to display and manage the edit menu, see Managing the Selection and the Edit Menu (page 59).
In response to a copy: or cut: message, you write the object or data represented by the selection to the pasteboard in as many different representations as you can. This operation involves the following steps (which assume a single pasteboard item):
1.
From the selection, identify or obtain the object or the binary data corresponding to the object.
51
Copy, Cut, and Paste Operations Copying and Cutting the Selection
Binary data must be encapsulated in an NSData object. If youre going to write another type of object to the pasteboard, it must be a property-list objectthat is, an object of one of the following classes: NSString, NSArray, NSDictionary, NSDate, NSNumber, or NSURL. (For more on property-list objects, see Property List Programming Guide .)
2.
If possible, generate one or more other representations of the object or data. For example, if in the previous step you created a UIImage object representing a selected image, you could use the UIImageJPEGRepresentation and UIImagePNGRepresentation functions to convert the image to a different representation.
3.
Obtain a pasteboard object. In many cases, this is the general pasteboard, which you can get through the generalPasteboard class method.
4.
Assign a suitable UTI for each representation of data written to the pasteboard item. See Pasteboard Concepts (page 48) for a discussion of this subject.
5.
Write the data to the first pasteboard item for each representation type:
To write a data object, send a setData:forPasteboardType: message to the pasteboard object. To write a property-list object, send a setValue:forPasteboardType: message to the pasteboard object.
6.
If the command is Cut (cut: method), remove the object represented by the selection from the apps data model and update your view.
Listing 6-1 shows implementations of the copy: and cut: methods. The cut: method invokes the copy: method and then removes the selected object from the view and the data model. Note that the copy: method archives a custom object to obtain an NSData object that it can pass to the pasteboard in setData:forPasteboardType:.
Listing 6-1 Copying and cutting operations
- (void)copy:(id)sender { UIPasteboard *gpBoard = [UIPasteboard generalPasteboard]; ColorTile *theTile = [self colorTileForOrigin:currentSelection]; if (theTile) { NSData *tileData = [NSKeyedArchiver archivedDataWithRootObject:theTile]; if (tileData) [gpBoard setData:tileData forPasteboardType:ColorTileUTI]; }
52
if (theTile) { CGPoint tilePoint = theTile.tileOrigin; [tiles removeObject:theTile]; CGRect tileRect = [self rectFromOrigin:tilePoint inset:TILE_INSET]; [self setNeedsDisplayInRect:tileRect]; } }
Obtain a pasteboard object. In many cases, this is the general pasteboard, which you can get through the generalPasteboard class method.
2.
Verify that the first pasteboard item contains data in a representation that your app can handle by calling the containsPasteboardTypes: method or the pasteboardTypes method and then examining the returned array of types. Note that you should have already performed this step in your implementation of canPerformAction:withSender:.
3.
If the first item of the pasteboard contains data that the app can handle, call one of the following methods to read it:
53
dataForPasteboardType: if the data to be read is encapsulated in an NSData object. valueForPasteboardType: if the data to be read is encapsulated in a property-list object (see
Add the object to the apps data model. Display a representation of the object in the user interface at the location specified by the user.
Listing 6-2 is an example of an implementation of the paste: method. It does the reverse of the combined cut: and copy: methods. The custom view first sees whether the general pasteboard holds its custom representation of data; if it does, it then reads the data from the pasteboard, adds it to the apps data model, and marks part of itselfthe current selectionfor redrawing.
Listing 6-2 Pasting data to a selection
- (void)paste:(id)sender { UIPasteboard *gpBoard = [UIPasteboard generalPasteboard]; NSArray *pbType = [NSArray arrayWithObject:ColorTileUTI]; ColorTile *theTile = [self colorTileForOrigin:currentSelection]; if (theTile == nil && [gpBoard containsPasteboardTypes:pbType]) { NSData *tileData = [gpBoard dataForPasteboardType:ColorTileUTI]; ColorTile *theTile = (ColorTile *)[NSKeyedUnarchiver unarchiveObjectWithData:tileData]; if (theTile) { theTile.tileOrigin = self.currentSelection; [tiles addObject:theTile]; CGRect tileRect = [self rectFromOrigin:currentSelection inset:TILE_INSET]; [self setNeedsDisplayInRect:tileRect]; } } }
Ending an Operation
When your implementation of the cut:, copy: or paste: command returns, the edit menu is automatically hidden. You can programmatically keep it visible if you want. For more information, see Dismissing the Edit Menu (page 63)
54
UIKit allows apps to substitute custom input views for the system keyboard. It also enables apps to have an accessory view above the system keyboard or custom input view. Additionally, it enables apps to play key-click sounds when users tap on a controls of an input view or input accessory view.
When the responder object becomes the first responder and inputView (or inputAccessoryView) is not nil, UIKit animates the input view into place below the parent view (or attaches the input accessory view to the top of the input view). The first responder can reload the input and accessory views by calling the reloadInputViews method of UIResponder. The UITextView class redeclares the inputView and inputAccessoryView properties as readwrite. Clients of UITextView objects need only obtain the input and input-accessory viewseither by loading a nib file or creating the views in codeand assign them to their properties. Custom view classes (and other
55
Custom Views for Data Input Input Views and Input Accessory Views
subclasses that inherit from UIResponder) should redeclare one or both of these properties and their backing instance variables and override the getter method for the propertythat is, dont synthesize the properties accessor methods. In their getter-method implementations, they should return it the view, loading or creating it if it doesnt already exist. You have a lot of flexibility in defining the size and content of an input view or input accessory view. Although the height of these views can be what youd like, they should be the same width as the system keyboard. If UIKit encounters an input view with a UIViewAutoresizingFlexibleHeight value in its autoresizing mask, it changes the height to match the keyboard. There are no restrictions on the number of subviews (such as controls) that input views and input accessory views may have. For more guidance on input views and input accessory views, see iOS Human Interface Guidelines . To load a nib file at run time, first create the input view or input accessory view in Interface Builder. Then at runtime get the apps main bundle and call loadNibNamed:owner:options: on it, passing the name of the nib file, the Files Owner for the nib file, and any options. This method returns an array of the top-level objects in the nib, which includes the input view or input accessory view. Assign the view to its corresponding property. For more on this subject, see Nib Files in Resource Programming Guide . Listing 7-1 illustrates a custom view class lazily creating its input accessory view in the inputAccessoryView getter method.
Listing 7-1 Creating an input accessory view programmatically
- (UIView *)inputAccessoryView { if (!inputAccessoryView) { CGRect accessFrame = CGRectMake(0.0, 0.0, 768.0, 77.0); inputAccessoryView = [[UIView alloc] initWithFrame:accessFrame]; inputAccessoryView.backgroundColor = [UIColor blueColor]; UIButton *compButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; compButton.frame = CGRectMake(313.0, 20.0, 158.0, 37.0); [compButton setTitle: @"Word Completions" forState:UIControlStateNormal]; [compButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [compButton addTarget:self action:@selector(completeCurrentWord:) forControlEvents:UIControlEventTouchUpInside]; [inputAccessoryView addSubview:compButton]; } return inputAccessoryView; }
56
The subviews of an input view and input accessory view can be anything you want. If they are buttons or other controls, you need to specify targets and actions for each control and implement the associated action methods to perform data input or manipulation. Just as it does with the system keyboard, UIKit posts UIKeyboardWillShowNotification, UIKeyboardDidShowNotification, UIKeyboardWillHideNotification, and UIKeyboardDidHideNotification notifications. The object observing these notifications can get geometry information related to the input view and input accessory view and adjust the edited view accordingly. See Keyboards and Input Methods (page 37) for examples and related information.
In your Xcode project, create a subclass of the UIView class. In the header file, indicate that the subclass conforms to the UIInputViewAudioFeedback protocol, as follows:
@interface KeyboardAccessoryView : UIView <UIInputViewAudioFeedback> { }
2.
In the implementation file for your UIView subclass, implement the enableInputClicksWhenVisible method, as follows:
- (BOOL) enableInputClicksWhenVisible { return YES; }
3.
Finally, in the Interface Builder document for your custom input or accessory view, select the View object. In the Identity inspector, set the class for the object to be your UIView subclass.
57
The system automatically manages the audio session for custom input clicks, including audio ducking as needed. (For information on audio sessions, see Audio Session Programming Guide .)
58
The edit menu is a contextual menu that is displayed to offer commands that can be performed on a selection such as a word in a text view or an image. The edit menu is an integral part of copy, cut, and paste operations, for which it displays (potentially) the commands Copy, Cut, Paste, Select, and Select All. However, you can add custom menu items to the edit menu to perform other kinds of actions on selections.
When your app determines that the user has requested the edit menuwhich could be the action of making a selectionyou should complete the following steps to display the menu:
1.
Call the sharedMenuController class method of UIMenuController to get the global menu-controller instance. Compute the boundaries of the selection and with the resulting rectangle call the setTargetRect:inView: method. The edit menu is displayed above or below this rectangle, depending how close the selection is to the top or bottom of the screen. Call the setMenuVisible:animated: method (with YES for both arguments) to animate the display of the edit menu above or below the selection.
2.
3.
59
Displaying and Managing the Edit Menu Managing the Selection and the Edit Menu
Listing 8-1 illustrates how you might display the edit menu in an implementation of the touchesEnded:withEvent: method for handling copy, cut, and paste operations. (Note that the example omits the section of code that handles the selection.) This code snippet also shows the custom view sending itself a becomeFirstResponder message to ensure that it is the first responder for the subsequent copy, cut, and paste operations.
Listing 8-1 Displaying the edit menu
if ([theTouch tapCount] == 2
// bring up edit menu. UIMenuController *theMenu = [UIMenuController sharedMenuController]; CGRect selectionRect = CGRectMake (currentSelection.x, currentSelection.y, SIDE, SIDE); [theMenu setTargetRect:selectionRect inView:self]; [theMenu setMenuVisible:YES animated:YES];
} }
The menu initially includes all commands for which the first responder has corresponding UIResponderStandardEditActions method implementations (copy:, paste:, and so on). Before the menu is displayed, however, the system sends a canPerformAction:withSender: message to the first responder, which in many cases is the custom view itself. In its implementation of this method, the responder evaluates whether the command (indicated by the selector in the first argument) is applicable in the current context. For example, if the selector is paste: and there is no data in the pasteboard of a type the view can handle, the responder should return NO to suppress the Paste command. If the first responder does not implement the canPerformAction:withSender: method, or does not handle the given command, the message travels up the responder chain. Listing 8-2 shows an implementation of the canPerformAction:withSender: method that looks for message matching the cut:, copy:, and paste: selectors; it enables or disables the Copy, Cut, and Paste menu commands based on the current selection context and, for paste, the contents of the pasteboard.
60
Displaying and Managing the Edit Menu Managing the Selection and the Edit Menu
Listing 8-2
if (action == @selector(paste:) ) retValue = (theTile == nil) && [[UIPasteboard generalPasteboard] containsPasteboardTypes: [NSArray arrayWithObject:ColorTileUTI]]; else if ( action == @selector(cut:) || action == @selector(copy:) ) retValue = (theTile != nil); else retValue = [super canPerformAction:action withSender:sender]; return retValue; }
Note that the final else clause in this method calls the superclass implementation to give any superclass a chance to handle commands that the subclass chooses to ignore. Note that a menu command, when acted upon, can change the context for other menu commands. For example, if the user selects all objects in the view, the Copy and Cut commands should be included in the menu. In this case the responder can, while the menu is still visible, call update on the menu controller; this results in the reinvocation of canPerformAction:withSender: on the first responder.
61
Displaying and Managing the Edit Menu Adding Custom Items to the Edit Menu
An instance of the UIMenuItem class represents a custom menu item. UIMenuItem objects have two properties, a title and an action selector, which you can change at any time. To implement a custom menu item, you must initialize a UIMenuItem instance with these properties, add the instance to the menu controllers array of custom menu items, and then implement the action method for handling the command in the appropriate responder subclass. Other aspects of implementing a custom menu item are common to all code that uses the singleton UIMenuController object. In a custom or overridden view, you set the view to be the first responder, get the shared menu controller, set a target rectangle, and then display the edit menu with a call to setMenuVisible:animated:. The simple example in Listing 8-3 adds a custom menu item for changing a custom views color between red and black.
Listing 8-3 Implementing a Change Color menu item
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {} - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {} - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *theTouch = [touches anyObject]; if ([theTouch tapCount] == 2) { [self becomeFirstResponder]; UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Change Color" action:@selector(changeColor:)];
62
Displaying and Managing the Edit Menu Dismissing the Edit Menu
UIMenuController *menuCont = [UIMenuController sharedMenuController]; [menuCont setTargetRect:self.frame inView:self.superview]; menuCont.arrowDirection = UIMenuControllerArrowLeft; menuCont.menuItems = [NSArray arrayWithObject:menuItem]; [menuCont setMenuVisible:YES animated:YES]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)changeColor:(id)sender { if ([self.viewColor isEqual:[UIColor blackColor]]) { self.viewColor = [UIColor redColor]; } else { self.viewColor = [UIColor blackColor]; } [self setNeedsDisplay]; }
Note: The arrowDirection property of UIMenuController, shown in Listing 8-3, allows you to specify the direction the arrow attached to the edit menu points at its target rectangle.
The system may hide the edit menu at any time. For example, it hides the menu when an alert is displayed or the user taps in another area of the screen. If you have state or a display that depends on whether the edit menu is visible, you should listen for the notification named UIMenuControllerWillHideMenuNotification and take an appropriate action.
63
The UIKit framework includes several classes whose purpose is to display text in an apps user interface: UITextView, UITextField, UILabel, and UIWebView, as described in Displaying Text Content in iOS (page 11). Text views, created from the UITextView class, are meant to display large amounts of text. Underlying UITextView is a powerful layout engine called Text Kit. If you need to customize the layout process or you need to intervene in that behavior, you can use Text Kit. For smaller amounts of text and special needs requiring custom solutions, you can use alternative, lower-level technologies, as described in Lower Level Text-Handling Technologies (page 79). Text Kit is a set of classes and protocols in the UIKit framework providing high-quality typographical services that enable apps to store, lay out, and display text with all the characteristics of fine typesetting, such as kerning, ligatures, line breaking, and justification. Text Kit is built on top of Core Text, so it provides the same speed and power. UITextView is fully integrated with Text Kit; it provides editing and display capabilities that enable users to input text, specify formatting attributes, and view the results. The other Text Kit classes provide text storage and layout capabilities. Figure 9-1 shows the position of Text Kit among other iOS text and graphics frameworks.
Figure 9-1 Text Kit Framework Position
Text Kit gives you complete control over text rendering in user interface elements. In addition to UITextView, UITextField and UILabel are built on top of Text Kit, and it seamlessly integrates with animations, UICollectionView and UITableView. Text Kit is designed with a fully extensible object-oriented architecture that supports subclassing, delegation, and a thorough set of notifications enabling deep customization.
64
Using Text Kit to Draw and Manage Text Primary Text Kit Objects
An NSTextContainer object defines a region where text can be laid out. Typically, a text container defines a rectangular area, but by creating a subclass of NSTextContainer you can create other shapes: circles, pentagons, or irregular shapes, for example. Not only does a text container describe the outline of an area that can be filled with text, it maintains an array of Bezier paths that are exclusion zones within its area where text is not laid out. As it is laid out, text flows around the exclusion paths, providing a means to include graphics and other non-text layout elements.
NSTextStorage defines the fundamental storage mechanism of the Text Kits extended text-handling system. NSTextStorage is a subclass of NSMutableAttributedString that stores the characters and attributes
manipulated by the text system. It ensures that text and attributes are maintained in a consistent state across editing operations. In addition to storing the text, an NSTextStorage object manages a set of client NSLayoutManager objects, notifying them of any changes to its characters or attributes so that they can relay and redisplay the text as needed. An NSLayoutManager object orchestrates the operation of the other text handling objects. It intercedes in operations that convert the data in an NSTextStorage object to rendered text in a views display area. It maps Unicode character codes to glyphs and oversees the layout of the glyphs within the areas defined by NSTextContainer objects.
65
Note: NLayoutManager, NSTextStorage, and NSTextContainer can be accessed from subthreads as long as the app guarantees the access from a single thread.
For reference information about UITextView, see UITextView Class Reference . NSTextContainer is described in NSTextContainer Class Reference for iOS , NSLayoutManager in NSLayoutManager Class Reference for iOS , and NSTextStorage in NSTextStorage Class Reference for iOS .
Text Attributes
Text Kit handles three kinds of text attributes: character attributes, paragraph attributes, and document attributes. Character attributes include traits such as font, color, and subscript, which can be associated with an individual character or a range of characters. Paragraph attributes are traits such as indentation, tabs, and line spacing. Document attributes include documentwide traits such as paper size, margins, and view zoom percentage.
Character Attributes
An attributed string stores character attributes as key-value pairs in NSDictionary objects. The key is an attribute name, represented by an identifier (an NSString constant) such as NSFontAttributeName. Figure 9-3 shows an attributed string with an attribute dictionary applied to a range within the string. ns_attributed_string.eps
Cocoa_Text_Architecture Apple, Inc.
Figure 9-3
Conceptually, each character in an attributed string has an associated dictionary of attributes. Typically, however, an attribute dictionary applies to a longer range of characters, a run of text. The NSAttributedString class provides methods that take a character index and return the associated attribute dictionary and the range to which its attribute values apply, such as attributesAtIndex:effectiveRange:.
66
You can assign any attribute key-value pair you choose to a range of characters, in addition to working with predefined attributes. You add the attributes to the appropriate character range in the NSTextStorage object using the NSMutableAttributedString method addAttribute:value:range:. You can also create an NSDictionary object containing the names and values of a set of custom attributes and add them to the character range in a single step using the addAttributes:range: method. To make use of your custom attributes, you need a custom subclass of NSLayoutManager to work with them. Your subclass should override the drawGlyphsForGlyphRange:atPoint: method. Your override can first call the superclass to draw the glyph range and then draw your own attributes on top. Alternatively, your override can draw the glyphs entirely your own way.
Paragraph Attributes
Paragraph attributes affect the way the layout manager arranges lines of text into paragraphs on a page. The text system encapsulates paragraph attributes in objects of the NSParagraphStyle class. The value of one of the predefined character attributes, NSParagraphStyleAttributeName, points to an NSParagraphStyle object containing the paragraph attributes for that character range. Attribute fixing ensures that only one NSParagraphStyle object pertains to the characters throughout each paragraph. Paragraph attributes include traits such as alignment, tab stops, line-breaking mode, and line spacing (also known as leading ).
Document Attributes
Document attributes pertain to a document as a whole. Document attributes include traits such as paper size, margins, and view zoom percentage. Although the text system has no built-in mechanism to store document attributes, NSAttributedString initialization methods such as initWithRTF:documentAttributes: can populate an NSDictionary object that you provide with document attributes derived from a stream of RTF or HTML data. Conversely, methods that write RTF data, such as RTFFromRange:documentAttributes:, write document attributes if you pass a reference to an NSDictionary object containing them with the message.
Attribute Fixing
Editing attributed strings can cause inconsistencies that must be cleaned up by attribute fixing. The UIKit extensions to NSMutableAttributedString define the fixAttributesInRange: method to fix inconsistencies among attachment, character, and paragraph attributes. These methods ensure that attachments dont remain after their attachment characters are deleted, that character attributes apply only to characters available in that font, and that paragraph attributes are consistent throughout paragraphs.
67
Using Text Kit to Draw and Manage Text Changing Text Storage Programmatically
68
Using Text Kit to Draw and Manage Text Working with Font Objects
font information to choose the glyphs used during text layout. You use font objects by passing them to methods that accept them as a parameter. Font objects are immutable, so it is safe to use them from multiple threads in your app. You dont create UIFont objects using the alloc and init methods; instead, you use preferredFontForTextStyle: with a text style constant or fontWithName:size:. You can also use a font descriptor to create a font with fontWithDescriptor:size:. These methods check for an existing font object with the specified characteristics, returning it if there is one. Otherwise, they look up the font data requested and create the appropriate font object.
Text Styles
Text styles, introduced in iOS 7, are semantic descriptions of the intended uses for fonts and are implemented by a mechanism known as Dynamic Type. Text styles are organized by use and represented by constants defined in UIFontDescriptor.h, as shown in Table 9-1. The actual font used for the purpose described by a text style can vary based on a number of dynamic considerations, including the users content size category preference, which is represented by the UIApplication property preferredContentSizeCategory. To acquire a font object for a given text style, you pass the corresponding constant to the UIFont method preferredFontForTextStyle:. To acquire a font descriptor for a text style, pass the constant to the UIFontDescriptor method preferredFontDescriptorWithTextStyle:. (See Font Descriptors (page 70) for more information about font descriptors.)
Table 9-1 Constant UIFontTextStyleHeadline UIFontTextStyleSubheadline UIFontTextStyleBody UIFontTextStyleFootnote UIFontTextStyleCaption1 UIFontTextStyleCaption2 Text style constants Usage
The font used for headings. The font used for subheads. The font used for body text. The font used for footnotes. The font used for standard captions. The font used for alternate captions.
Text styles bring many advantages to apps through the Dynamic Type mechanism, all of which enhance the readability of your text. Dynamic Type responds in a coordinated way to user preferences and responds to accessibility settings for enhanced legibility and oversize type. That is, when you call
69
Using Text Kit to Draw and Manage Text Working with Font Objects
preferredFontForTextStyle:, the specific font returned includes traits which vary according to user
preferences and context, including tracking (letter-spacing) adjustments, in addition to being tuned for the use specified by the particular text style constant. The fonts returned using text style constants are meant to be used for all text in an app other than text in user interface elements, such as buttons, bars, and labels. Naturally, you need to choose text styles that look right in your app. Its also important to observe the UIContentSizeCategoryDidChangeNotification so that you can relay out the text when the user changes the content size category. When your app receives that notification, it should send the invalidateIntrinsicContentSize message to views positioned by Auto Layout or send setNeedsLayout to user interface elements positioned manually. And it should invalidate preferred fonts or font descriptors and acquire new ones as needed.
Font Descriptors
Font descriptors, instantiated from the UIFontDescriptor class, provide a way to describe a font with a dictionary of attributes and are used to create UIFont objects. In particular, you can make a UIFont object from a font descriptor, you can get a descriptor from a UIFont object, and you can change a descriptor and use it to make a new font object. You can also use a font descriptor to specify custom fonts provided by an app. Font descriptors can be archived, which is an advantage working with text styles. You should not cache font objects specified by text styles because they are dynamictheir characteristics vary over time according to user preferences. But you can cache a font descriptor to preserve a description of a font, and then unarchive it later and use it to create a font object with the same characteristics. You can use font descriptors to query the system for available fonts that match particular attributes, and then create instances of fonts matching those attributes, such as names, traits, languages, and other features. For example, you can use a font descriptor to retrieve all the fonts matching a given font family name, using the family names defined by the CSS standard, as shown in Listing 9-1.
Listing 9-1 Font family name matching
UIFontDescriptor *helveticaNeueFamily = [UIFontDescriptor fontDescriptorWithFontAttributes: @{ UIFontDescriptorFamilyAttribute: @"Helvetica Neue" }]; NSArray *matches = [helveticaNeueFamily matchingFontDescriptorsWithMandatoryKeys: nil];
70
Using Text Kit to Draw and Manage Text Working with Font Objects
The matchingFontDescriptorsWithMandatoryKeys: method as shown returns an array of font descriptors for all the Helvetica Neue fonts on the system, such as HelveticaNeue, HelveticaNeue-Medium, HelveticaNeue-Light, HelveticaNeue-Thin, and so on. You can modify the fonts returned by preferredFontForTextStyle: by applying symbolic traits, such as bold, italic, expanded, and condensed. You can use font descriptors to modify particular traits, as shown in Listing 9-2.
Listing 9-2 Font trait modification
UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle: UIFontTextStyleBody]; UIFontDescriptor *boldFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits: UIFontDescriptorTraitBold]; UIFont *boldFont = [UIFont fontWithDescriptor: boldFontDescriptor size: 0.0];
This code snippet first retrieves a font descriptor for the body text style, then modifies that font descriptor to specify the bold trait, and finally uses the UIFont class method fontWithDescriptor:size: to return an actual font object for the body text style with a bold trait. Passing a size value of 0.0 with fontWithDescriptor:size: specifies that the size attribute originally returned with the font descriptor is preserved. This behavior is desired, of course, because the font size is determined by the dynamic type mechanism.
71
Using Text Kit to Draw and Manage Text Working with Font Objects
available. Properties such as ascender, capHeight, xHeight, and so on, all correspond to standard font metrics information. Figure 9-4 shows how the font metrics apply to glyph dimensions, and Table 9-2 lists property names that correlate with various metrics. See the property descriptions for more specific information.
Figure 9-4 Font metrics
Font metrics and related UIFont methods Properties xHeight ascender capHeight lineHeight descender pointSize
72
Using Text Kit to Draw and Manage Text Laying Out Text
Controls text storage and text container objects Generates glyphs from characters Computes glyph locations and stores the information Manages ranges of glyphs and characters Draws glyphs in text views when requested by the view Computes bounding box rectangles for lines of text Controls hyphenation Manipulates character attributes and glyph properties
In the model-view-controller paradigm, the layout manager is the controller. NSTextStorage, a subclass of NSMutableAttributedString, provides part of the model, holding a string of text characters with attributes such as typeface, style, color, and size. NSTextContainer can also be considered part of the model because it models the geometric layout of the page on which the text is laid out. UITextView (or another UIView object) provides the view in which the text is displayed. NSLayoutManager serves as the controller for the text system because it translates characters in the text storage object into glyphs, lays them out in lines according to the dimensions of one or more text container objects, and coordinates the text display in one or more text view objects.
73
Using Text Kit to Draw and Manage Text Laying Out Text
74
Using Text Kit to Draw and Manage Text Laying Out Text
proposing a line fragment rectangle intersecting one of the regions defined by the exclusion paths, the text container returns an adjusted line fragment rectangle excluding that region. This process is illustrated in Figure 9-6.
Figure 9-5 Line fragment fitting
You can also create this arrangement in code, as shown in Listing 9-3. This code could be in a view controller, for example, a subclass of UIViewController, that has an NSTextContainer property named textContainer.
Listing 9-3 Object creation for a single text flow
NSTextStorage* textStorage = [[NSTextStorage alloc] initWithString:string]; NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; [textStorage addLayoutManager:layoutManager]; self.textContainer = [[NSTextContainer alloc] initWithSize:self.view.bounds.size]; [layoutManager addTextContainer:self.textContainer];
75
Using Text Kit to Draw and Manage Text Laying Out Text
This configuration is limited by having only one text container and one text view. In such an arrangement, the text flows uninterrupted within the area defined by the text container. Page breaks, multicolumn layout, and more complex layouts cant be accommodated by this arrangement. By using multiple text containers, each with an associated text view, more complex layout arrangements are possible. For example, to support page breaks, an app can configure the text objects as shown in Figure 9-7.
Figure 9-7 Object configuration for paginated text
Each text container corresponds to a page of the document. The views displaying the text can be embedded in a custom view object that your app provides as a background for the text views. This custom view, in turn, can be embedded in a UIScrollView object to enable the user to scroll through the documents pages.
76
Using Text Kit to Draw and Manage Text Laying Out Text
A multicolumn document can be modeled with a similar arrangement of objects, as shown in Figure 9-8.
Figure 9-8 Object configuration for multicolumn text
Instead of having one text container correspond to a single page, there are now two text containersone for each column on the page. Each text container controls a portion of the document. As the text is displayed, glyphs are first laid out in the top-left container. When there is no more room in that view, the layout manager informs its delegate that it has finished filling the container. The delegate can check to see whether theres more text that needs to be laid out and add another text container if necessary. The layout manager proceeds to lay out text in the next container, notifies the delegate when finished, and so on. Again, a custom view (depicted as a blue rectangle) provides a canvas for these text columns.
77
Using Text Kit to Draw and Manage Text Laying Out Text
Not only can you have multiple text containers, you can also have multiple NSLayoutManager objects accessing the same text storage. Figure 9-9 illustrates an object arrangement with multiple layout managers. The effect of this arrangement is to provide multiple views of the same text. If the user alters the text in the top view, the change is immediately reflected in the bottom view (assuming the location of the change is within the bottom views bounds).
Figure 9-9 Object configuration for multiple views of the same text
78
Most apps can use the high-level text display classes and Text Kit for all their text handling. You might have an app, however, that requires the lower level programmatic interfaces from the Core Text, Core Graphics, and Core Animation frameworks as well as other APIs in UIKit itself.
79
For information about the string-drawing extensions to NSString, see NSString UIKit Additions Reference . To learn more about CATextLayer, CALayer, and the other classes of Core Animation, read Core Animation Programming Guide .
Core Text
Core Text is a technology for custom text layout and font management. App developers typically have no need to use Core Text directly. Text Kit is built on top of Core Text, giving it the same advantages, such as speed and sophisticated typographic capability. In addition, Text Kit provides a great deal of infrastructure that you must build for yourself if you use Core Text. However, the Core Text API is accessible to developers who must use it directly. It is intended to be used by apps that have their own layout enginesfor example, a word processor that has its own page layout engine can use Core Text to generate the glyphs and position them relative to each other. Core Text is implemented as a framework that publishes an API similar to that of Core Foundationsimilar in that it is procedural (ANSI C) but is based on object-like opaque types. This API is integrated with both Core Foundation and Core Graphics. For example, Core Text uses Core Foundation and Core Graphics objects in many input and output parameters. Moreover, because many Core Foundation objects are toll-free bridged with their counterparts in the Foundation framework, you may use some Foundation objects in the parameters of Core Text functions. Note: If you use Core Text or Core Graphics to draw text, remember that you must apply a flip transform to the current text matrix to have text displayed in its proper orientationthat is, with the drawing origin at the upper-left corner of the strings bounding box.
Core Text has two major parts: a layout engine and font technology, each backed by its own collection of opaque types.
80
Core Text objects at runtime form a hierarchy that is reflective of the level of the text being processed (see Figure 10-1 (page 81)). At the top of this hierarchy is the framesetter object (CTFramesetterRef). With an attributed string and a graphics path as input, a framesetter generates one or more frames of text (CTFrameRef). As the text is laid out in a frame, the framesetter applies paragraph styles to it, including such attributes as alignment, tab stops, line spacing, indentation, and line-breaking mode. To generate frames, the framesetter calls a typesetter object (CTTypesetterRef). The typesetter converts the characters in the attributed string to glyphs and fits those glyphs into the lines that fill a text frame. (A glyph is a graphic shape used to represent a character.) A line in a frame is represented by a CTLine object (CTLineRef). A CTFrame object contains an array of CTLine objects. A CTLine object, in turn, contains an array of glyph runs, represented by objects of the CTRunRef type. A glyph run is a series of consecutive glyphs that have the same attributes and direction. Although a typesetter object returns CTLine objects, it composes those lines from arrays of glyph runs.
Figure 10-1 Core Text layout objects
Using functions of the CTLine opaque type, you can draw a line of text from an attributed string without having to go through the CTFramesetter object. You simply position the origin of the text on the text baseline and request the line object to draw itself.
81
Font objects (CTFontRef) are initialized with a point size and specific characteristics (from a transformation matrix). You can query the font object for its character-to-glyph mapping, its encoding, glyph data, and metrics such as ascent, leading, and so on. Core Text also offers an automatic font-substitution mechanism called font cascading. Font descriptor objects (CTFontDescriptorRef) are typically used to create font objects. Instead of dealing with a complex transformation matrix, they allow you to specify a dictionary of font attributes that include such properties as PostScript name, font family and style, and traits (for example, bold or italic). Font collection objects (CTFontCollectionRef) are groups of font descriptors that provide services such as font enumeration and access to global and custom font collections.
Its possible to convert UIFont objects to CTFont objects by calling CTFontCreateWithName, passing the font name and point size encapsulated by the UIFont object.
82
Note: In addition to the NSString regular-expression feature described here, iOS provides more complete support for regular expressions with the NSRegularExpression class. The ICU User Guide describes how to construct ICU regular expressions (http://userguide.icu-project.org/strings/regexp).
If you specify the NSRegularExpressionSearch option in these methods, the only other NSStringCompareOptions options you may specify are NSCaseInsensitiveSearch and NSAnchoredSearch. If a regular-expression search does not find a match or the regular-expression syntax is malformed, these methods return an NSRange structure with a value of {NSNotFound, 0}. Listing 10-1 gives an example of using the NSString regular-expression API.
Listing 10-1 Finding a substring using a regular expression
// finds phone number in format nnn-nnn-nnnn NSRange r; NSString *regEx = @"[0-9]{3}-[0-9]{3}-[0-9]{4}"; r = [textView.text rangeOfString:regEx options:NSRegularExpressionSearch]; if (r.location != NSNotFound) { NSLog(@"Phone number is %@", [textView.text substringWithRange:r]); } else { NSLog(@"Not found."); }
Because these methods return a single range value for the substring matching the pattern, certain regular-expression capabilities of the ICU library are either not available or have to be programmatically added. In addition, NSStringCompareOptions options such as backward search, numeric search, and diacritic-insensitive search are not available and capture groups are not supported.
83
When testing the returned range, you should be aware of certain behavioral differences between searches based on literal strings and searches based on regular-expression patterns. Some patterns can successfully match and return an NSRange structure with a length of 0 (in which case the location field is of interest). Other patterns can successfully match against an empty string or, in those methods with a range parameter, with a zero-length search range.
You can read the ICU 4.2 API documentation and user guide at http://icu-project.org/apiref/icu4c/index.html.
84
To adopt UIKeyInput, you must implement the three methods it declares: hasText, insertText:, and deleteBackward. To do the actual drawing of the text, you may use any of the technologies summarized in this chapter. However, for simple text input, such as for a single line of text in a custom control, the UIStringDrawing and CATextLayer APIs are most appropriate. Listing 10-2 illustrates the UIKeyInput implementation of a custom view class. The textStore property in this example is an NSMutableString object that serves as the backing store of text. The implementation either appends or removes the last character in the string (depending on whether an alphanumeric key or the Delete key is pressed) and then redraws textStore.
Listing 10-2 Implementing simple text entry
- (BOOL)hasText { if (textStore.length > 0) { return YES; } return NO; }
85
Lower Level Text-Handling Technologies Communicating with the Text Input System
- (void)drawRect:(CGRect)rect { CGRect rectForText = [self rectForTextWithInset:2.0]; // custom method [self.theColor set]; UIRectFrame(rect); [self.textStore drawInRect:rectForText withFont:self.theFont]; }
To actually draw the text in the view, this code uses the drawInRect:withFont: from the UIStringDrawing category on NSString.
86
Lower Level Text-Handling Technologies Communicating with the Text Input System
The following section describes the general responsibilities of a custom text view that communicates with the text input system. A Guided Tour of a UITextInput Implementation (page 91) examines the most important classes and methods of a typical implementation of UITextInput.
The text view must do its own text layout and font management; for this purpose, the Core Text framework is recommended. (Core Text (page 80) gives an overview of Core Text.) The class should also adopt and implement the UIKeyInput protocol and should set the necessary properties of the UITextInputTraits protocol.
87
Lower Level Text-Handling Technologies Communicating with the Text Input System
The general architecture of the client and system sides of the text input system are diagrammed in Figure 10-2.
Figure 10-2 Paths of communication with the text input system
The text input system calls the UITextInput methods that the text view implements. Many of these methods request information about specific text positions and text ranges from the text view and pass the same information back to the class in other method calls. The reasons for these exchanges of text positions and text ranges are summarized in Tasks of a UITextInput Object (page 89). Text positions and text ranges in the text input system are represented by instances of custom classes. Text Positions and Text Ranges (page 88) discusses these objects in more detail. The text view also maintains references to a tokenizer and an input delegate. The text view calls methods declared by the UITextInputDelegate protocol to notify a system-provided input delegate about external changes in text and selection. The text input system communicates with a tokenizer object to determine the granularity of text unitsfor example, character, word, and paragraph. The tokenizer is an object that adopts the UITextInputTokenizer protocol. The text view includes a property (declared by UITextInput) that holds a reference to the tokenizer.
88
Lower Level Text-Handling Technologies Communicating with the Text Input System
Although UITextPosition itself declares no methods or properties, it is an essential part of the information exchanged between a text document and the text input system. The text input system requires an object to represent a location in the text instead of, say, an integer or a structure. Moreover, a UITextPosition object can serve a practical purpose by representing a position in the visible text when the string backing the text has a different offset to that position. This happens when the string contains invisible formatting characters, such as with RTF and HTML documents, or embedded objects, such as an attachment. The custom UITextPosition class can account for these invisible characters when locating the string offsets of visible characters. In the simplest casea plain text document with no embedded objectsa custom UITextPosition object can encapsulate a single offset or index integer.
UITextRange declares a simple interface in which two of its properties are starting and ending custom UITextPosition objects. The third property holds a Boolean value that indicates whether the range is empty
Returning and replacing text by text range. Given a range, either return the text in that range or replace that text with text provided by the text input system.
textInRange: replaceRange:withText:
Computing text ranges and text positions.Create and return a UITextRange object (or, simply, a text range) given two text positions; or create and return a UITextPosition object (or, simply, a text position) given a text position and an offset.
positionFromPosition:offset: positionFromPosition:inDirection:offset: textRangeFromPosition:toPosition:
89
Lower Level Text-Handling Technologies Communicating with the Text Input System
Evaluating text positions. Compare two text positions or return the offset from one text position to another.
comparePosition:toPosition: offsetFromPosition:toPosition:
Answering layout questions. Determine a text position or text range by extending in a given layout direction.
positionWithinRange:farthestInDirection: characterRangeByExtendingPosition:inDirection:
Hit-testing. Given a point, return the closest text position or text range.
closestPositionToPoint: closestPositionToPoint:withinRange: characterRangeAtPoint:
Returning rectangles for text ranges and text positions. Return the rectangle that encloses a text range or the rectangle at the text position of the caret.
firstRectForRange: caretRectForPosition:
The UITextInput object might also choose to implement one or more optional protocol methods. These enable it to return text styles (font, text color, background color) beginning at a specified text position and to reconcile visible text position and character offset (for those UITextPosition objects where these values are not the same). When changes occur in the text view due to external reasonsthat is, they aren't caused by calls from the text input systemthe UITextInput object should send textWillChange:, textDidChange:, selectionWillChange:, and selectionDidChange: messages to the input delegate (which it holds a reference to). For example, when users tap a text view and you set the range of selected text to place the insertion point under the finger, you would send selectionWillChange: before you change the selected range, and you send selectionDidChange: after you change the range.
90
Lower Level Text-Handling Technologies Communicating with the Text Input System
Tokenizers
Tokenizers are objects that determine whether a text position is within or at the boundary of a text unit with a given granularity. When queried by the text input system, a tokenizer returns ranges of text units with a given granularity or the boundary text position for a text unit with a given granularity. Currently defined granularities are character, word, sentence, paragraph, line, and document; enum constants of the UITextGranularity type represent these granularities. Granularities of text units are always evaluated with reference to a storage or layout direction. The text input system uses the tokenizer in a variety of ways. For example, the keyboard might require the last sentences worth of context to figure out what the user is trying to type. Or, if the user is pressing the Option-left arrow key (on an external keyboard), the text system queries the tokenizer to find the information it needs to move to the previous word. A tokenizer is an instance of a class that conforms to the UITextInputTokenizer protocol. The UITextInputStringTokenizer class provides a default base implementation of the UITextInputTokenizer protocol that is suitable for all supported languages. If you require a tokenizer with an entirely new interpretation of text units of varying granularity, you should adopt UITextInputTokenizer and implement all of its methods. Otherwise you should subclass UITextInputStringTokenizer to provide app-specific information about layout directions. When you initialize a UITextInputStringTokenizer object, you supply it with the view adopting the UITextInput protocol. In turn, the UITextInput object should lazily create its tokenizer object in the getter method of the tokenizer property.
91
Lower Level Text-Handling Technologies Communicating with the Text Input System
Note: For reasons of space, the guided tour shows implementations of the UITextInput methods that are most important or illustrative. However, it is possible to extrapolate from these chosen implementations to the others of the protocols. The code was taken from the SimpleTextInput sample code project.
custom subclass of UITextRange called IndexedRange. These subclasses simply encapsulate a single index value and an NSRange value based on two of those indexes. Listing 10-3 shows the declaration of these classes.
Listing 10-3 Declaring the IndexedPosition and IndexedRange classes
@interface IndexedPosition : UITextPosition { NSUInteger _index; id <UITextInputDelegate> _inputDelegate; } @property (nonatomic) NSUInteger index; + (IndexedPosition *)positionWithIndex:(NSUInteger)index; @end
@interface IndexedRange : UITextRange { NSRange _range; } @property (nonatomic) NSRange range; + (IndexedRange *)rangeWithNSRange:(NSRange)range;
@end
Both classes declare class factory methods to vend instances. Listing 10-4 shows the implementation of these methods as well as the methods declared by the UITextRange class.
Listing 10-4 Implementing the IndexedPosition and IndexedRange classes
@implementation IndexedPosition @synthesize index = _index;
92
Lower Level Text-Handling Technologies Communicating with the Text Input System
+ (IndexedPosition *)positionWithIndex:(NSUInteger)index { IndexedPosition *pos = [[IndexedPosition alloc] init]; pos.index = index; return [pos autorelease]; }
@end
+ (IndexedRange *)rangeWithNSRange:(NSRange)nsrange { if (nsrange.location == NSNotFound) return nil; IndexedRange *range = [[IndexedRange alloc] init]; range.range = nsrange; return [range autorelease]; }
93
Lower Level Text-Handling Technologies Communicating with the Text Input System
if (markedTextRange.location != NSNotFound) { [_text replaceCharactersInRange:markedTextRange withString:text]; selectedNSRange.location = markedTextRange.location + text.length; selectedNSRange.length = 0; markedTextRange = NSMakeRange(NSNotFound, 0); } else if (selectedNSRange.length > 0) { [_text replaceCharactersInRange:selectedNSRange withString:text]; selectedNSRange.length = 0; selectedNSRange.location += text.length; } else { [_text insertString:text atIndex:selectedNSRange.location]; selectedNSRange.location += text.length; } _textView.text = _text; _textView.markedTextRange = markedTextRange; _textView.selectedTextRange = selectedNSRange; }
94
Lower Level Text-Handling Technologies Communicating with the Text Input System
Even though the structure of the deleteBackward method implemented by EditableCoreTextView is identical to the insertText: method, there are appropriate differences in how the selected and marked text ranges are adjusted. Another difference is that the deleteCharactersInRange: method is called on the backing mutable string rather than replaceCharactersInRange:withString:.
- (void)replaceRange:(UITextRange *)range withText:(NSString *)text { IndexedRange *r = (IndexedRange *)range; NSRange selectedNSRange = _textView.selectedTextRange; if ((r.range.location + r.range.length) <= selectedNSRange.location) { selectedNSRange.location -= (r.range.length - text.length); } else { // Need to also deal with overlapping ranges. } [_text replaceCharactersInRange:r.range withString:text]; _textView.text = _text; _textView.selectedTextRange = selectedNSRange; }
95
Lower Level Text-Handling Technologies Communicating with the Text Input System
When the text property of SimpleCoreTextView changes (as shown in the implementation of replaceRange:withText:), SimpleCoreTextView lays out the text again and redraws it using Core Text functions.
The setter method for the selectedTextRange in Listing 10-8 simply sets the selected-text range on the embedded text view. The setMarkedText:selectedRange: method is more complex because, as you may recall, the range of marked text contains within it the range of selected text (even if the range merely identifies the caret), and these ranges have to be reconciled to reflect the situation after the insertion of text.
Listing 10-8 Setting the range of selected text and setting the marked text
- (void)setSelectedTextRange:(UITextRange *)range { IndexedRange *r = (IndexedRange *)range; _textView.selectedTextRange = r.range; }
96
Lower Level Text-Handling Technologies Communicating with the Text Input System
if (markedTextRange.location != NSNotFound) { if (!markedText) markedText = @""; [_text replaceCharactersInRange:markedTextRange withString:markedText]; markedTextRange.length = markedText.length; } else if (selectedNSRange.length > 0) { [_text replaceCharactersInRange:selectedNSRange withString:markedText]; markedTextRange.location = selectedNSRange.location; markedTextRange.length = markedText.length; } else { [_text insertString:markedText atIndex:selectedNSRange.location]; markedTextRange.location = selectedNSRange.location; markedTextRange.length = markedText.length; } selectedNSRange = NSMakeRange(selectedRange.location + markedTextRange.location, selectedRange.length);
Note that EditableCoreTextView replaces the text by calling the replaceCharactersInRange:withString: method on its mutable string object, which it then assigns to the text property of the embedded text view.
97
Lower Level Text-Handling Technologies Communicating with the Text Input System
The offsetFromPosition:toPosition: method should satisfy the opposite request and return a value specifying the offset between two text positions. EditableCoreTextView implements it as shown in Listing 10-10.
Listing 10-10 Implementing offsetFromPosition:toPosition:
- (NSInteger)offsetFromPosition:(UITextPosition *)from toPosition:(UITextPosition *)toPosition { IndexedPosition *f = (IndexedPosition *)from; IndexedPosition *t = (IndexedPosition *)toPosition; return (t.index - f.index); }
Finally, the text input system frequently asks a text view for a text range that falls between two text positions. Listing 10-11 shows an implementation of textRangeFromPosition:toPosition: that returns this range.
Listing 10-11 Implementing textRangeFromPosition:toPosition:
- (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition { IndexedPosition *from = (IndexedPosition *)fromPosition; IndexedPosition *to = (IndexedPosition *)toPosition; NSRange range = NSMakeRange(MIN(from.index, to.index), ABS(to.index from.index)); return [IndexedRange rangeWithNSRange:range]; }
98
Lower Level Text-Handling Technologies Communicating with the Text Input System
Returning Rectangles
When a correction bubble appears and when the user types in Japanese, the text input system sends firstRectForRange: and caretRectForPosition: to the text view. The purpose of both of these methods is to return a rectangle enclosing either a range of text or the caret that marks the insertion point. The EditableCoreTextView class implements the first of these methods by calling a method of its embedded text view that maps the range to an enclosing rectangle (see Listing 10-12). Before returning the rectangle, it converts it to the local coordinate system.
Listing 10-12 An implementation of firstRectForRange:
- (CGRect)firstRectForRange:(UITextRange *)range { IndexedRange *r = (IndexedRange *)range; CGRect rect = [_textView firstRectForNSRange:r.range]; return [self convertRect:rect fromView:_textView]; }
The embedded text view in this case performs the lions share of the work. Using Core Text functions, it computes the rectangle that encloses the range of text and returns it, as shown in Listing 10-13.
Listing 10-13 Mapping text range to enclosing rectangle
- (CGRect)firstRectForNSRange:(NSRange)range; { int index = range.location; NSArray *lines = (NSArray *) CTFrameGetLines(_frame); for (int i = 0; i < [lines count]; i++) { CTLineRef line = (CTLineRef) [lines objectAtIndex:i]; CFRange lineRange = CTLineGetStringRange(line); int localIndex = index - lineRange.location; if (localIndex >= 0 && localIndex < lineRange.length) { int finalIndex = MIN(lineRange.location + lineRange.length, range.location + range.length); CGFloat xStart = CTLineGetOffsetForStringIndex(line, index, NULL); CGFloat xEnd = CTLineGetOffsetForStringIndex(line, finalIndex, NULL); CGPoint origin; CTFrameGetLineOrigins(_frame, CFRangeMake(i, 0), &origin); CGFloat ascent, descent;
99
Lower Level Text-Handling Technologies Communicating with the Text Input System
return CGRectMake(xStart, origin.y - descent, xEnd - xStart, ascent + descent); } } return CGRectNull; }
For caretRectForPosition:, the approach you take would be somewhat different. Selection affinity (selectionAffinity) is a factor to consider; more importantly, keep in mind that the height and width of the caret rectangle can be different from the bounding rectangle returned from firstRectForRange:.
Hit Testing
Another area where the text input system asks the text view to map between the display of text and the storage of text is hit testing. Given a point in the text view (the text input system asks), what is the corresponding text position or text range? The UITextInput methods it calls for this information are closestPositionToPoint:, closestPositionToPoint:withinRange:, and characterRangeAtPoint:. Listing 10-14 illustrates how EditableCoreTextView implements the first of these methods.
Listing 10-14 An implementation of closestPositionToPoint:
Here, as with the methods that return rectangles for text ranges or text positions, EditableCoreTextView calls a method of its embedded view that uses Core Text to calculate the character index that corresponds to the point. Listing 10-15 illustrates how the embedded view accomplishes this.
Listing 10-15 Mapping a point to a character index
100
Lower Level Text-Handling Technologies Communicating with the Text Input System
for (int i = 0; i < lines.count; i++) { if (point.y > origins[i].y) { CTLineRef line = (CTLineRef) [lines objectAtIndex:i]; return CTLineGetStringIndexForPosition(line, point); } } return } _text.length;
- (void)tap:(UITapGestureRecognizer *)tap { if (![self isFirstResponder]) { _textView.editing = YES; [self becomeFirstResponder]; } else { [self.inputDelegate selectionWillChange:self];
101
NSInteger index = [_textView closestIndexToPoint:[tap locationInView:_textView]]; _textView.markedTextRange = NSMakeRange(NSNotFound, 0); _textView.selectedTextRange = NSMakeRange(index, 0);
[self.inputDelegate selectionDidChange:self]; } }
The method you use for checking a document for misspelled words is
rangeOfMisspelledWordInString:range:startingAt:wrap:language:; the method used for obtaining
the list of possible replacement words is guessesForWordRange:inString:language:. You call these methods in the given order. To check an entire document, you call the two methods in a loop, resetting the starting offset to the character following the corrected word at each cycle through the loop, as shown in Listing 10-17.
Listing 10-17 Spell-checking a document
102
NSString *theText = textView.text; NSRange stringRange = NSMakeRange(0, theText.length-1); NSArray *guesses; BOOL done = NO;
while (!done) { currentRange = [textChecker rangeOfMisspelledWordInString:theText range:stringRange startingAt:currentOffset wrap:NO language:theLanguage]; if (currentRange.location == NSNotFound) { done = YES; continue; } guesses = [textChecker guessesForWordRange:currentRange inString:theText language:theLanguage]; NSLog(@"---------------------------------------------"); NSLog(@"Word misspelled is %@", [theText substringWithRange:currentRange]); NSLog(@"Possible replacements are %@", guesses); NSLog(@" "); currentOffset = currentOffset + (currentRange.length-1); } }
The UITextChecker class includes methods for telling the text checker to ignore or learn words. Instead of just logging the misspelled words and their possible replacements, as the method in Listing 10-17 does, you should display some user interface that allows users to select correct spellings, tell the text checker to ignore or learn a word, and proceed to the next word without making any changes. One possible approach for an iPad app would be to use a popover view that lists the guesses in a table view and includes buttons such as Replace, Learn, Ignore, and so on. You may also use UITextChecker to obtain completions for partially entered words and display the completions in a table view in a popover view. For this task, you call the completionsForPartialWordRange:inString:language: method, passing in the range in the given
103
string to check. This method returns an array of possible words that complete the partially entered word. Listing 10-18 shows how you might call this method and display a table view listing the completions in a popover view.
Listing 10-18 Presenting a list of word completions for the current partial string
- (IBAction)completeCurrentWord:(id)sender {
self.completionRange = [self computeCompletionRange]; // The UITextChecker object is cached in an instance variable NSArray *possibleCompletions = [textChecker completionsForPartialWordRange:self.completionRange inString:self.textStore language:@"en"];
CGSize popOverSize = CGSizeMake(150.0, 400.0); completionList = [[CompletionListController alloc] initWithStyle:UITableViewStylePlain]; completionList.resultsList = possibleCompletions; completionListPopover = [[UIPopoverController alloc] initWithContentViewController:completionList]; completionListPopover.popoverContentSize = popOverSize; completionListPopover.delegate = self; // rectForPartialWordRange: is a custom method CGRect pRect = [self rectForPartialWordRange:self.completionRange]; [completionListPopover presentPopoverFromRect:pRect inView:self permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; }
104
This table describes the changes to Text Programming Guide for iOS .
Notes Revised introduction figure. Fixed typos. Added material describing Text Kit and typographical concepts and made associated changes throughout. Changed name from "Text, Web, and Editing Programming Guide for iOS". Updated information on pasteboard persistence and keyboard adjustment. Also made many small corrections. Made some minor corrections. Added "Playing Input Clicks" to the "Custom Views for Data Input" chapter. Incorporates information on copy-cut-paste operations, edit menu management, and custom data-input views. Made some minor corrections and clarifications, especially with keyboard management. Title changed from "Text and Web Programming Guide for iOS".
2011-03-07
2011-01-10 2010-11-15
2010-07-07
Changed the title from "Text and Web Programming Guide for iPhone OS." First version of a document that describes the technologies and techniques for displaying and managing text and web content in iOS.
2010-05-11
105
Apple Inc. Copyright 2013 Apple Inc. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, mechanical, electronic, photocopying, recording, or otherwise, without prior written permission of Apple Inc., with the following exceptions: Any person is hereby authorized to store documentation on a single computer for personal use only and to print copies of documentation for personal use provided that the documentation contains Apples copyright notice. No licenses, express or implied, are granted with respect to any of the technology described in this document. Apple retains all intellectual property rights associated with the technology described in this document. This document is intended to assist application developers to develop applications only for Apple-labeled computers. Apple Inc. 1 Infinite Loop Cupertino, CA 95014 408-996-1010 Apple, the Apple logo, iPad, iPhone, OS X, Quartz, Safari, TrueType, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. Helvetica and Times are registered trademarks of Heidelberger Druckmaschinen AG, available from Linotype Library GmbH. Java is a registered trademark of Oracle and/or its affiliates. iOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license.
Even though Apple has reviewed this document, APPLE MAKES NO WARRANTY OR REPRESENTATION, EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS DOCUMENT, ITS QUALITY, ACCURACY, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. AS A RESULT, THIS DOCUMENT IS PROVIDED AS IS, AND YOU, THE READER, ARE ASSUMING THE ENTIRE RISK AS TO ITS QUALITY AND ACCURACY. IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES RESULTING FROM ANY DEFECT OR INACCURACY IN THIS DOCUMENT, even if advised of the possibility of such damages. THE WARRANTY AND REMEDIES SET FORTH ABOVE ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer, agent, or employee is authorized to make any modification, extension, or addition to this warranty. Some states do not allow the exclusion or limitation of implied warranties or liability for incidental or consequential damages, so the above limitation or exclusion may not apply to you. This warranty gives you specific legal rights, and you may also have other rights which vary from state to state.