With the larger screen of the iPad, not only simple text entry but complex text processing and custom input are now compelling possibilities for many applications. Applications can have features such as custom text layout, font management, autocorrection, custom keyboards, spell-checking, selection-based modification, and multistage input. iOS 3.2 includes several technologies that make these features realizable. This chapter describes these technologies and tells you what you need to do to incorporate them in your applications.
The UIKit framework includes support for custom input views and input accessory views. Your application can substitute its own input view for the system keyboard when users edit text or other forms of data in a view. For example, an application could use a custom input view to enter characters from a runic alphabet. You may also attach an input accessory view to the system keyboard or to a custom input view; this accessory view runs along the top of the main input view and can contain, for example, controls that affect the text in some way or labels that display some information about the text.
To get this feature if your application is using UITextView
and UITextField
objects for text editing, simply assign custom views to the inputView
and inputAccessoryView
properties. Those custom views are shown when the text object becomes first responder.
You are not limited to input views and input accessory views in framework-supplied text objects. Any class inheriting directly or indirectly from UIResponder
(usually a custom view) can specify its own input view and input accessory view. The UIResponder
class declares two properties for input views and input accessory views:
@property (readonly, retain) UIView *inputView; |
@property (readonly, retain) UIView *inputAccessoryView; |
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 views—either by loading a nib file or creating the views in code—and assign them to their properties. Custom view classes (and other 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 property—that is, don’t synthesize the properties’ accessor methods. In their getter-method implementations, they should return it the view, loading or creating it if it doesn’t 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 you’d like, they should be the same width as the system keyboard. If UIKit encounters an input view with anUIViewAutoresizingFlexibleHeight
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 iPad 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 application’s main bundle and call loadNibNamed:owner:options:
on it, passing the name of the nib file, the File’s 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; |
} |
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.
You can implement custom views that allow users to enter text at an insertion point and delete characters before that insertion point when they tap the Delete key. An instant-messaging application, for example, could have a view that allows users to enter their part of a conversation.
You can acquire this capability for simple text entry by subclassing UIView
or any other view class that inherits from UIResponder
and adopting the UIKeyInput
protocol. When an instance of your view class becomes first responder, UIKit displays the system keyboard. UIKeyInput
itself adopts the UITextInputTraits
protocol, so you can set keyboard type, return-key type, and other attributes of the keyboard.
Note: Only a small subset of the available keyboards and languages are available to classes that adopt the UIKeyInput
protocol.
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 “Facilities for Text Drawing and Text Processing.” 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 7-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 7-2 Implementing simple text entry
- (BOOL)hasText { |
if (textStore.length > 0) { |
return YES; |
} |
return NO; |
} |
- (void)insertText:(NSString *)theText { |
[self.textStore appendString:theText]; |
[self setNeedsDisplay]; |
} |
- (void)deleteBackward { |
NSRange theRange = NSMakeRange(self.textStore.length-1, 1); |
[self.textStore deleteCharactersInRange:theRange]; |
[self setNeedsDisplay]; |
} |
- (void)drawRect:(CGRect)rect { |
CGRect rectForText = [self rectForTextWithInset:2.0]; // custom method |
[self.theColor set]; |
UIRectFrame(rect); |
[self.textStore drawInRect:rectForText withFont:self.theFont]; |
} |
Note that this code uses the drawInRect:withFont:
from the UIStringDrawing
category on NSString
to actually draw the text in the view. See “Facilities for Text Drawing and Text Processing” for more about UIStringDrawing
.
The text input system of iOS manages the keyboard, interpreting taps as presses of specific keys in specific keyboards suitable for certain languages and sending the associated character to the target view for insertion. As explained in “Simple Text Input,” view classes must adopt the UIKeyInput
protocol to insert and delete characters at the caret (insertion point).
However, the text input system does more than simple text entry. It also manages autocorrection, and multistage input, which are all based upon the current selection and context. Multistage text input is required for ideographic languages such as hanji (Japanese) and hanzi (Chinese) that take input from phonetic keyboards. To acquire these features, a custom text view must communicate with the text input system by adopting the UITextInput
protocol and implementing the related client-side classes and protocols.
A class of a text document must adopt the UITextInput
protocol to communicate fully with the text input system. The class needs to inherit from UIResponder
and is in most cases a custom view. It must implement its own text layout and font management; for this purpose, the Core Text framework is recommended. (“Facilities for Text Drawing and Text Processing” gives an overview of Core Text.) The class should also adopt and implement the UIKeyInput
protocol, although it does inherit the default implementation of the UITextInputTraits
protocol.
The UITextInput
methods that the text document implements are called by the text input system. Many of these methods request text-position and text-range objects from the text document and pass text-position and text-range objects back to the text document in other method calls. The reasons for these exchanges of text positions and text ranges are summarized in “Tasks of a UITextInput Object.”
These text-position and text-range objects are custom objects that for the document represent locations and ranges in its displayed text. “Text Positions and Text Ranges” discusses these objects in more detail.
The UITextInput
-conforming document also maintains references to a tokenizer and an input delegate. The document calls methods declared by the UITextInputDelegate
protocol to notify a system-provided input delegate about changes in text and selection. It also communicates with a tokenizer object to determine the granularity of text units—for example, character, word, and paragraph. The tokenizer is an object that adopts the UITextInputTokenizer
protocol.
The client application must create two classes whose instances represent positions and ranges in the text of a document. These classes must be subclasses of UITextPosition
and UITextRange
, respectively.
Although UITextPosition
itself declares no interface, it is an essential part of the information exchanged between a text document and the text input system. The text 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 could account for these invisible characters when locating the string offsets of visible characters. In the simplest case—a plain text document with no embedded objects—a custom UITextPosition
object could encapsulate a single offset 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 (that is, has no length).
A text-document class adopting the UITextInput
protocol is required to implement most of the protocol’s methods and properties. With a few exceptions, these methods take custom UITextPosition
or UITextRange
objects as parameters or return one of these objects. At runtime the text system invokes these methods and, again in almost all cases, expects some object or value back.
The methods implemented by a UITextInput
objects can be divided into distinctive tasks:
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.
Evaluating text positions. Compare two text positions or return the offset from one text position to another.
Answering layout questions. Determine a text position or text range by extending in a given layout direction.
Hit-testing. Given a point, return the closest text position or text range.
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.
Returning and setting text by text range.
In addition, the UITextInput
object must maintain the range of the currently selected text and the range of the currently marked text, if any. Marked text, which is part of multistage text input, represents provisionally inserted text the user has yet to confirm. It is styled in a distinctive way. The range of marked text always contains within it a range of selected text, which might be a range of characters or the caret.
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).
At appropriate junctures, the UITextInput
object should send textWillChange:
, textDidChange:
, selectionWillChange:
, and selectionDidChange:
messages to the input delegate (which it holds a reference to).
Tokenizers are objects that can determine whether a text position is within or at the boundary of a text unit with a given granularity. A tokenizer returns ranges of text units with the granularity or the boundary text position for a text unit with the 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.
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 western-language keyboards. 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. If instead you need only to specify line granularities and directions affected by layout (left, right, up, and down), you should subclass UITextInputStringTokenizer
.
When you initialize a UITextInputStringTokenizer
object, you must supply it with the UITextInput
object. In turn, the UITextInput
object should lazily create its tokenizer object in the getter method of the tokenizer property.
The UIKit framework includes several classes whose main purpose is to display text in an application’s user interface: UITextField
, UILabel
, UITextView
, and UIWebView
. You might have an application, however, that requires greater flexibility than these classes afford; in other words, you want greater control over where and how your application draws and manipulates text. For these situations, iOS makes available programmatic interfaces from from the Core Text, Core Graphics, and Core Animation frameworks as well as from UIKit itself.
Note: If you use Core Text or Core Graphics to draw text, remember that you must apply a flip transform to the current graphics context to have text displayed in its proper orientation—that is, with the drawing origin at the upper-left corner of the string’s bounding box. In addition, text drawing in Core Text and Core Graphics requires a graphics context set with the text matrix.
Core Text is a technology for sophisticated text layout and font management. It is intended to be used by applications with a heavy reliance on text processing—for example, book readers and word processors. It is implemented as a framework that publishes a procedural (ANSI C) API. This API is consistent with that of Core Foundation and Core Graphics, and is integrated with these other frameworks. 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.
You should not use Core Text unless you want to do custom text layout.
Note: Although Core Text is new in iOS 3.2, the framework has been available in Mac OS X since Mac OS X v10.5. For a detailed description of Core Text and some examples of its usage (albeit in the context of Mac OS X), see Core Text Programming Guide.
Core Text has two major parts: a layout engine and font technology, each backed by its own collection of opaque types.
Core Text requires two objects whose opaque types are not native to it: an attributed string (CFAttributedStringRef
) and a graphics path (CGPathRef
). An attributed-string object encapsulates a string backing the displayed text and includes properties (or, “attributes”) that define stylistic aspects of the characters in the string—for example, font and color. The graphics path defines the shape of a frame of text, which is equivalent to a paragraph.
Core Text objects at runtime form a hierarchy that is reflective of the level of the text being processed (see ). 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 CFLine 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.
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.
Fonts are essential to text processing in Core Text. The typesetter object uses fonts (along with the source attributed string) to convert glyphs from characters and then position those glyphs relative to one another. A graphics context establishes the current font for all text drawing that occurs in that context. The Core Text font system handles Unicode fonts natively.
The font system includes objects of three opaque types: CTFont, CTFontDescriptor, and CTFontCollection.
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.
Core Text and the text layout and rendering facilities of the UIKit framework are not compatible. This incompatibility has the following implications:
You cannot use Core Text to compute the layout of text and then use APIs such as UIStringDrawing
to draw the text.
If your application uses Core Text, it does not have access to text-related UIKit features such as copy-paste. If you use Core Text and want these features, you must implement them yourself.
By default, UIKit does not do kerning, which can cause lines to be dropped.
UIStringDrawing
and CATextLayer
are programmatic facilities that are ideal for simple text drawing. UIStringDrawing
is a category on NSString
implemented by the UIKit framework. CATextLayer
is part of the Core Animation technology.
The methods of UIStringDrawing
enable iOS applications to draw strings at a given point (for single lines of text) or within a specified rectangle (for multiple lines). You can pass in attributes used in drawing—for example, font, line-break mode, and baseline adjustment. Some methods, given certain parameters such as font, line-breaking mode, and width constraints, return the size of a drawn string and thus let you compute the bounding rectangle for that string when you draw it.
The CATextLayer
class of Core Animation stores a plain string or attributed string as its content and offers a set of attributes that affect that content, such as font, font size, text color, and truncation behavior. The advantage of CATextLayer
is that (being a subclass of CALayer
) its properties are inherently capable of animation. Core Animation is associated with the QuartzCore framework.
Because instances of CATextLayer
know how to draw themselves in the current graphics context, you don’t need to issue any explicit drawing commands when using those instances.
To learn more about UIStringDrawing
, read NSString UIKit Additions Reference. To learn more about CATextLayer
, CALayer
, and the other classes of Core Animation, read Core Animation Programming Guide.
Core Graphics (or Quartz) is the system framework that handles two-dimensional imaging at the lowest level. Text drawing is one of its capabilities. Generally, because Core Graphics is so low-level, it is recommended that you use Core Text or one of the system’s other facilities for drawing text. However, drawing text with Core Graphics does bring some advantages. It gives you more control of the fonts you use when drawing and allows more precise rendering and placement of glyphs.
You select fonts, set text attributes, and draw text using functions of the CGContext opaque type. For example, you can call CGContextSelectFont
to set the font used, and then call CGContextSetFillColor
to set the text color. You then set the text matrix (CGContextSetTextMatrix
) and draw the text using CGContextShowGlyphsAtPoint
.
To learn more about the text-drawing API of Core Graphics, read “Text” in Quartz 2D Programming Guide.
The NSString
class of the Foundation framework includes a simple programmatic interface for regular expressions. You call one of three methods that return a range, passing in a specific option constant and a regular-expression string. If there is a match, the method returns the range of the substring. The option is the NSRegularExpressionSearch
constant, which is of bit-mask type NSStringCompareOptions
; this constant tells the method to expect a regular-expression pattern rather than a literal string as the search value. The supported regular expression syntax is that defined by ICU (International Components for Unicode).
Note: The NSString
regular-expression feature was introduced in iOS 3.2. The ICU User Guide describes how to construct ICU regular expressions (http://userguide.icu-project.org/strings/regexp).
The NSString
methods for regular expressions are the following:
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 7-3 gives an example of using the NSString
regular-expression API.
Listing 7-3 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 backward search, numeric search, and diacritic-insensitive search are not available and capture groups are not supported.
Note: As noted in “ICU Regular-Expression Support,” the ICU libraries related to regular expressions are included in iOS 3.2. However, you should only use the ICU facilities if the NSString
alternative is not sufficient for your needs.
When testing the returned range, you should be aware of certain behavioral differences between searches based on literal string 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.
A modified version of the libraries from 4.2.1 is included in iOS 3.2 at the BSD (non-framework) level of the system. ICU (International Components for Unicode) is an open-source project for Unicode support and software internationalization. The installed version of ICU includes those header files necessary to support regular expressions along with some modifications related to those interfaces. Table 7-1 lists these files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You can read the ICU 4.2 API documentation and user guide at http://icu-project.org/apiref/icu4c/index.html.
With an instance of the UITextChecker
class you can check the spelling of a document or offer suggestions for completing partially entered words. When spell-checking a document, a UITextChecker
object searches a document at a specified offset. When it detects a misspelled word, it can also return an array of possible correct spellings, ranked in the order which they should be presented to the user (that is, the most likely replacement word comes first). You typically use a single instance of UITextChecker
per document, although you can use a single instance to spell-check related pieces of text if you want to share ignored words and other state.
Note: The UITextChecker
class is intended for spell-checking and not for autocorrection. Autocorrection is a feature your text document can acquire by adopting the protocols and implementing the subclasses described in “Communicating with the Text Input System.”
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 7-4.
Listing 7-4 Spell-checking a document
- (IBAction)spellCheckDocument:(id)sender { |
NSInteger currentOffset = 0; |
NSRange currentRange = NSMakeRange(0, 0); |
NSString *theText = textView.text; |
NSRange stringRange = NSMakeRange(0, theText.length-1); |
NSArray *guesses; |
BOOL done = NO; |
NSString *theLanguage = [[UITextChecker availableLanguages] objectAtIndex:0]; |
if (!theLanguage) |
theLanguage = @"en_US"; |
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 7-4 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 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 string to check. This method returns an array of possible words that complete the partially entered word. Listing 7-5 shows how you might call this method and display a table view listing the completions in a popover view.
Listing 7-5 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]; |
} |
You can add a custom item to the edit menu used for showing the system commands Copy, Cut, Paste, Select, Select All, and Delete. When users tap this item, a command is issued that affects the current target in an application-specific way. The UIKit framework accomplishes this through the target-action mechanism. The tap of an item results in a action message being sent to the first object in the responder chain that can handle the message. Figure 7-3 shows an example of a custom menu item (“Change Color”).
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 controller’s 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 editing menu with a call to setMenuVisible:animated:
. The simple example in Listing 7-6 adds a custom menu item for changing a custom view’s color between red and black.
Listing 7-6 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:)]; |
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 {} |
- (BOOL)canBecomeFirstResponder { return YES; } |
- (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 7-6, is new in iOS 3.2. It allows you to specify the direction the arrow attached to the editing menu points at its target rectangle. Also new is the Delete menu command; if users tap this menu command, the delete:
method implemented by an object in the responder chain (if any) is invoked. The delete:
method is declared in the UIResponderStandardEditActions
informal protocol.
Last updated: 2010-04-13