第七章 Delegation and Text Input

Delegation and Text Input


In this chapter, we will introduce delegation, a recurring design pattern of Cocoa Touch development.
In addition, you will see how to use the debugger that Xcode provides to find and fix problems in your
code.

这一章中,我们会介绍代理模式,即是可复用的设计模式; 另外,你可以在这一章中学习使用调试工具。


By the end of the chapter, the HypnoNerd user will be able to display hypnotic messages on the screen
using a text field (Figure 7.1).

在这篇文章的末尾,你可以使用text field在屏幕中看到一大堆令人催眠的消息。

第七章 Delegation and Text Input_第1张图片


Text Fields
Open the HypnoNerd application that you started in the previous chapter.


You have already seen one way to display text on your user interfaces using a UILabel. Now let’s take
a look at another way to display text using a UITextField. An instance of UITextField allows the user
to modify the text, much like a username or password field on a website.

使用UITextField,可以编辑文字。


Open BNRHypnosisViewController.m and modify the loadView to add a UITextField to its view.

- (void)loadView
{
CGRect frame = [UIScreen mainScreen].bounds;
BNRHypnosisView *backgroundView = [[BNRHypnosisView alloc] initWithFrame:frame];
CGRect textFieldRect = CGRectMake(40, 70, 240, 30);
UITextField *textField = [[UITextField alloc] initWithFrame:textFieldRect];
// Setting the border style on the text field will allow us to see it more easily
textField.borderStyle = UITextBorderStyleRoundedRect;
[backgroundView addSubview:textField];
self.view = backgroundView;
}
Build and run the application and you should see the text field on the Hypnotize tab. Tap on the text
field, and the keyboard will slide up from the bottom of the screen, allowing you to input text. To
understand how this is happening under the hood, you need to understand the first responder.

重新编译并运行,你可以看到一个输入框,点击它,会在屏幕中弹出键盘。为了理解底层发生了什么,你需要去了解

首个响应者(first responder)


UIResponder
UIResponder is an abstract class in the UIKit framework. It is the superclass of three classes that you
have already encountered:

UIResponder是ui-toolkit其中的一个抽象类,它是以下3个类的父类:
• UIView
• UIViewController
• UIApplication


UIResponder defines methods for handling (or “responding to”) events: touch events, motion events
(like a shake), and remote control events (like pausing or playing). Subclasses override these methods
to customize how they respond to events.

UIResponder 用户响应事件(类似于android的 OnClickListener?)


With touch events, it is obvious which view the user has touched. Touch events are sent directly to that
view. You saw an example of this in Chapter 5.
What about the other types of events? The UIWindow has a pointer called firstResponder which
indicates who should respond to the other types of events. When you select a text field, for example,
the window moves its firstResponder pointer to that text field. Motion and remote control events are
sent to the first responder.
Figure 7.2 firstResponder

第七章 Delegation and Text Input_第2张图片

UIWindow有一个firstResponder指针用户标明谁会先响应其他类型的事件。当你选择一个text field的时候,window对象会将指针指向

这个输入框,这样所有的motion和remote control事件都会发给输入框了。


当输入框成为firstResponder,就会显示它的键盘。相反者会消失键盘。如果一个view要成为firstResponder,就发送becomeFirstResponder消息,

这样就会显示出键盘,如果要消息键盘,则发送resignFirstResponder消息。

Most views refuse to become first responder; they do not want to steal focus from the currently
selected text field or text view. An instance of UISlider, for example, handles touch events but will
never accept first responder status.

大多数的视图拒绝成为first responder,它们并不想从text filed或者text view中夺取focus。比如UISilder控件,会

处理touch事件,但不会成为first responder。


Configuring the keyboard
The keyboard’s appearance is determined by a set of the UITextField’s properties called
UITextInputTraits. Let’s modify some of these to give the text field some placeholder text and to
modify the keyboard’s return type.

可以通过改变UITextField的属性,UITextInputTraits来改变键盘的显示;让我们改变其中的一些属性给text filed提供

一些提示文字以及修改键盘的返回类型。


- (void)loadView
{
CGRect frame = [UIScreen mainScreen].bounds;
BNRHypnosisView *backgroundView = [[BNRHypnosisView alloc] initWithFrame:frame];
CGRect textFieldRect = CGRectMake(40, 70, 240, 30);
UITextField *textField = [[UITextField alloc] initWithFrame:textFieldRect];
// Setting the border style on the text field will allow us to see it more easily
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.placeholder = @"Hypnotize me";
textField.returnKeyType = UIReturnKeyDone;
[backgroundView addSubview:textField];
self.view = backgroundView;
}
Build and run the application. Now the text field has a placeholder string that will be displayed
until the user types in some text. Also, the return key now says Done instead of the default Return.
Figure 7.3 shows what the interface looks like with these changes.

重新编译程序并运行,你就可以看到text field中有提示语以及键盘的返回类型改变了。


Figure 7.3 Configured text field

第七章 Delegation and Text Input_第3张图片

If you tap the Done key, you will notice that nothing happens. Changing the return key type has no
impact on the functionality of the return key. In fact, the return key does not do anything automatically;
you have to implement the return key functionality yourself. Before you do that, though, let’s take a
look at some of the other useful properties that you can use to configure the keyboard.


autocapitalizationType This determines how capitalization is handled. The options are
none, words, sentences, or all characters.

autocapitalizationType  自动大写类型


autocorrectionType This will suggest and correct unknown words. This value can be
YES or NO.

autocorrectionType  提示自动纠错


enablesReturnKeyAutomatically This value can be YES or NO. If set to yes, the return key will be
disabled if no text has been typed. As soon as any text is entered,

the return key becomes enabled.

enablesReturnKeyAutomatically  当设置为YES的时候,当text field没有输入内容的时候,返回按钮会disable;


keyboardType This determines the type of keyboard that will be displayed.

Some examples are the ASCII keyboard, email address
keyboard, number pad, and the URL keyboard.

keyboardType  改变键盘的类型,有ASCII 码,数字以及URL 等


secureTextEntry Setting this to YES makes the text field behave like a password
field, hiding the text that is entered.

secureTextEntry  当设置为yes的时候会让text field成为一个密码输入框,这样就可以隐藏输入的文字


Delegation
You have already seen the Target-Action pattern. This is one form of callbacks that is used by UIKit:
When a button is tapped, it sends its action message to its target. This typically triggers code that you
have written.

你已经看到过Target-Action模式,类似于设置setListener


A button’s life is relatively simple. For objects with more complex lives, like a text field, Apple uses
the delegation pattern. You introduce the text field to one of your objects: “This is your delegate, when
anything interesting happens in your life, send a message to him.” The text field keeps a pointer to its
delegate. Many of the message it sends to its delegates are informative: “OK, I am done editing!”. Here
are some of those:
复杂的控件就会用到Delegate,比如设置其他对象为text filed的代理,当text filed相关的事件发生时,
就可以向它的代理发送消息。(基于监听者模式)

- (void)textFieldDidEndEditing:(UITextField *)textField;
- (void)textFieldDidBeginEditing:(UITextField *)textField;
Notice that it always sends itself as the first argument to the delegate method.
要注意消息的第一个参数用于是发送者对象。


Some of the message it sends to its delegate are queries: “I am about to end editing and hide the
keyboard. OK?” Here are some of those:
一些发送给代理的消息时以询问的方式:比如我想要结束编辑并隐藏键盘了,怎么样?
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField;
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField;
- (BOOL)textFieldShouldClear:(UITextField *)textField;
- (BOOL)textFieldShouldReturn:(UITextField *)textField;


You are now going to make your BNRHypnosisViewController the delegate of the text field. You will
implement the textFieldShouldReturn: method. When you run it, you will see that the method gets
called automatically when the user taps the Done button.
现在设置BNRHypnosisViewController为text filed的代理。你需要在BNRHypnosisViewController中实现
textFieldShouldReturn: 方法,当用户点击键盘中的Done按钮,这个方法就会被调用。


In BNRHypnosisViewController.m, update loadView to set the delegate property of the UITextField
to point at the BNRHypnosisViewController.
在BNRHypnosisViewController.m中,修改loadView,设置UITextFiled的delegate为BNRHypnosisViewController;


- (void)loadView
{
CGRect frame = [UIScreen mainScreen].bounds;
BNRHypnosisView *backgroundView = [[BNRHypnosisView alloc] initWithFrame:frame];
CGRect textFieldRect = CGRectMake(40, 70, 240, 30);
UITextField *textField = [[UITextField alloc] initWithFrame:textFieldRect];
// Setting the border style on the text field will allow us to see it more easily
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.placeholder = @"Hypnotize me";
textField.returnKeyType = UIReturnKeyDone;
// There will be a warning on this line. We will discuss it shortly.
textField.delegate = self;
[backgroundView addSubview:textField];
self.view = backgroundView;
}


The method textFieldShouldReturn: takes in just one argument: the text field whose return key was
tapped. For now, the application will just print the text of the text field to the console.
In BNRHypnosisViewController.m, implement the textFieldShouldReturn:. Be very careful that
there are no typos or capitalization errors, or the method will not be called. The selector of the message
the text field sends must exactly match the selector of the method implemented.
在BNRHypnosisViewController.m中实现相关的代理方法,要注意的是方法的名字不要写错。
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSLog(@"%@", textField.text);
return YES;
}


Build and run the application, type some text into the text field, and tap the return key. The text should
print to the console.
Notice that you did not need to implement all of text field’s delegate methods, just the one that you
cared about. At runtime the text field will ask its delegate if it implements a method before calling it.
要注意并不是所有的代理方法都需要实现的,在运行中,text field调用相关方法前,会询问它的代理是否实现相关的方法。


Protocols
For every object that can have a delegate, there is a corresponding protocol that declares the messages
that the object can send its delegate. The delegate implements methods from the protocol for events it
is interested in. When a class implements methods from a protocol, it is said to conform to the protocol.
(If you are coming from Java or C#, you would use the word “interface” instead of “protocol”.)
因为每个对象都可以由相关的代理,那么必然有对应的协议提供给代理去实现(类似于android的interface.)


The protocol for UITextField’s delegate looks like this:
@protocol UITextFieldDelegate <NSObject>
@optional
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField;
- (void)textFieldDidBeginEditing:(UITextField *)textField;
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField;
- (void)textFieldDidEndEditing:(UITextField *)textField;
- (BOOL)textField:(UITextField *)textField
                                 shouldChangeCharactersInRange:(NSRange)range
                                 replacementString:(NSString *)string;
- (BOOL)textFieldShouldClear:(UITextField *)textField;
- (BOOL)textFieldShouldReturn:(UITextField *)textField;
@end


This protocol, like all protocols, is declared with the directive @protocol followed by its name,
UITextFieldDelegate. The NSObject in angled brackets refers to the NSObject protocol and tells us
that UITextFieldDelegate includes all of the methods in the NSObject protocol. 
上面的协议,是以@protocol开头,以@end结束,其中尖括号中NSObject 表示 UITextField继承了NSObject中
协议的所有方法。


The methods specific
to UITextFieldDelegate are declared next, and the protocol is closed with an @end directive.
Note that a protocol is not a class; it is simply a list of method declarations. You cannot create
instances of a protocol, it cannot have instance variables, and these methods are not implemented
anywhere in the protocol. Instead, implementation is left to each class that conforms to the protocol.
要注意的是协议并不是类,它无法被实例化,也没有实例变量,
它只有在相关的代理中实现。


The UITextFieldDelegate protocol is part of the iOS SDK. Protocols in the iOS SDK have reference
pages in the developer documentation where you can see what methods are declared. You can also
write your own protocol. You will do that in Chapter 22.
UITextFieldDelegate的协议是属于ios sdk中的一部分。你可以在相关的文档中查看协议,你也可以实现
自己的协议,这会在22章中介绍。


Methods declared in a protocol can be required or optional. By default, protocol methods are required.
If a protocol has optional methods, these are preceded by the directive @optional. Looking back at the
UITextFieldDelegate protocol, you can see that all of its methods are optional. This is typically true
of delegate protocols.
协议中方法可以使required或者optional,默认是required。


Before sending an optional message, the object first asks its delegate if it is okay to send that message
by sending another message, respondsToSelector:. Every object implements this method, which
checks at runtime whether an object implements a given method. You can turn a method selector into
a value that you can pass as an argument with the @selector() directive. For example, UITextField
could implement a method that looks like this:

对于optional 的消息,你需要在发送相关的消息前,发送respondsToSelector给代理查询消息是否存在;

每个对象都实现了这个消息,它会在runtime的时候查询相关的方法是否实现。 你可以使用@selector()创建respondsToSelector的参数。




- (void)clearButtonTapped
{
// textFieldShouldClear: is an optional method,
// so we check first
SEL clearSelector = @selector(textFieldShouldClear:);
if ([self.delegate respondsToSelector:clearSelector]) {
if ([self.delegate textFieldShouldClear:self]) {
self.text = @"";
}
}
}
If a method in a protocol is required, then the message will be sent without checking first. This means
that if the delegate does not implement that method, an unrecognized selector exception will be thrown,
and the application will crash.
如果protocol的属性时required(类似于抽象方法,一定要实现的),那么消息在发送之前就没必要先check。
当代理没有实现这个方法的时候,就会抛出selector无法识别的异常,并且程序会crash。


To prevent this from happening, the compiler will insist that a class implement the required methods
in a protocol. But for the compiler to know to check for implementations of a protocol’s required
methods, the class must explicitly state that it conforms to a protocol. This is done either in the
class header file or the class extension: the protocols that a class conforms to are added to a commadelimited
list inside angled brackets in the interface declaration.
为了避免出现这种问题,编译器会确保代理有实现这个协议。为了让编译器知道代理需要实现某个协议,需要在头文件中
或者在类的扩展中显示得声明:协议以尖括号的方式在Interface中添加,多个协议可以以逗号的方式隔离。


In BNRHypnosisViewController.m, declare that BNRHypnosisViewController conforms to the
UITextFieldDelegate protocol in the class extension. The reason for adding it to the class extension
rather than the header file is the same reason as always: add to the class extension if the information
(conforming to a particular protocol in this case) does not need to be publicly visible, and add it to the
header file if other objects do need to know about the information.
同样也可以在BNRHypnosisViewController.m 类扩展中添加协议。加在这里的好处是:隐藏,private;


@interface BNRHypnosisViewController () <UITextFieldDelegate>
@end
Build the application again. Now that you have declared that BNRHypnosisViewController conforms
to the UITextFieldDelegate protocol, the warning from the line of code where you set the delegate
disappears. Furthermore, if you want to implement additional methods from the UITextFieldDelegate
protocol in BNRHypnosisViewController, those methods will now be auto-completed by Xcode.
这样协议中方法就可以自动在代理中添加实现?(类似于eclipse?)


Many classes have a delegate pointer, and it is nearly always a weak reference to prevent strong
reference cycles. In this case, for example, your view controller indirectly owns the text field. If the
text field owned its delegate, you would have a strong reference cycle that would cause a memory leak.
许多类都有代理的指针,但这些指针一般都是弱指针,以避免出现强引用环;比如现在这个例子,view controller已经
拥有text filed,如果text filed也拥有它的代理,那么就会因为强引用环而出现内存泄露。


(只要有对象的引用,对象就不会被销毁);
(只要引用关系构成环状,不论中间有几个对象,都会有强引用环的问题)


Adding the Labels to the Screen
To make things a little interesting, you are going to add instances of UILabel to the screen at random
positions. In BNRHypnosisViewController.m, implement a new method that will draw a given string
on the screen twenty times at random positions.
为了让事情更有趣,我们在屏幕中动态添加多个UILabel. 在BNRHypnosisViewController.m,实现一个在随机问题
绘制多个string的方法。


- (void)drawHypnoticMessage:(NSString *)message
{
for (int i = 0; i < 20; i++) {
UILabel *messageLabel = [[UILabel alloc] init];
// Configure the label's colors and text
messageLabel.backgroundColor = [UIColor clearColor];
messageLabel.textColor = [UIColor whiteColor];
messageLabel.text = message;
// This method resizes the label, which will be relative
// to the text that it is displaying
[messageLabel sizeToFit];
// Get a random x value that fits within the hypnosis view's width
int width =
(int)(self.view.bounds.size.width - messageLabel.bounds.size.width);
int x = arc4random() % width;
// Get a random y value that fits within the hypnosis view's height
int height =
(int)(self.view.bounds.size.height - messageLabel.bounds.size.height);
int y = arc4random() % height;
// Update the label's frame
CGRect frame = messageLabel.frame;
frame.origin = CGPointMake(x, y);
messageLabel.frame = frame;
// Add the label to the hierarchy
[self.view addSubview:messageLabel];
}
}
In BNRHypnosisViewController.m, update the textFieldShouldReturn: method to call this new
method, passing in the text field’s text, clear the text that the user typed, and then dismiss the keyboard
by calling resignFirstResponder.
在BNRHypnosisViewController.m中,修改textFieldShouldReturn:,调用drawHypnoticMessage方法,并清空textField的值,
然后调用resignFirstResponder去隐藏键盘。


- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSLog(@"%@", textField.text);
[self drawHypnoticMessage:textField.text];
textField.text = @"";
[textField resignFirstResponder];
return YES;
}


Build and run the application, and enter some text into the text field. After tapping the return key, the
text should be displayed on instances of UILabel across the view.
重新编译并运行,你就会看到多个UILabel。


Motion Effects
添加神奇效果


iOS devices have a lot of powerful components embedded within them. A few of these – the
accelerometer, magnetometer, and gyroscope – help determine the orientation of the device. They are
how the device knows, for example, whether to display in portrait or landscape orientation. Starting
in iOS 7, Apple introduced a way for applications to easily take advantage of these sensors by adding
built-in parallax.
ios 设备添加了很多强悍的硬件设备,一些传感器用于控制设备的方向。 在ios7以后,apple提供了一种简单的方法
让应用也可以使用这些设备。


When you drive down the road, the signs along the shoulder appear to move much more quickly than
trees in the distance. Your brain interprets this difference in apparent speed as movement in space. This
visual effect is called “parallax”. With iOS 7, you have probably noticed this on the home screen where
the icons appear to move relative to the wallpaper when you tilt the device. It is used subtly (and not so
subtlety) in various places across the operating system and bundled apps, including the red badges on
Home screen icons, the volume changer pop-up, and alert views.


Applications can access the same technology that powers those effects by using the
UIInterpolatingMotionEffect class. Instances are given an axis (either horizontal or vertical), a key
path (which property of the view do you want to impact), and a relative minimum and maximum value
(how much the key path is allowed to sway in either direction).


In BNRHypnosisViewController.m, modify the drawHypnoticMessage: method to add a vertical and
horizontal motion effect to each label that allows its center to sway 25 points in either direction.


[self.view addSubview:messageLabel];
UIInterpolatingMotionEffect *motionEffect;
motionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x"
type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
motionEffect.minimumRelativeValue = @(-25);
motionEffect.maximumRelativeValue = @(25);
[messageLabel addMotionEffect:motionEffect];
motionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y"
type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
motionEffect.minimumRelativeValue = @(-25);
motionEffect.maximumRelativeValue = @(25);
[messageLabel addMotionEffect:motionEffect];
}


In order to test motion effects, the application must be running on a device. If you have a device
provisioned for developer use, build and run the application on the device. Add some hypnotic
messages to the view, and tilt the device slightly relative to your face. You will notice the magical
illusion of depth that the motion effects provide.
添加了一个特效,它基于Ios的硬件设备。



For the More Curious: main() and UIApplication
A C application begins by executing a main function. An Objective-C application is no different, but
you have not seen main() in any of your iOS applications. Let’s take a look now.


Open main.m in the HypnoNerd project navigator. It looks like this:
//打开main.m,这里就是程序的入口,可以指定UIApplication’s delegate;
每个ios应用都要一个main入口。
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv,
nil, NSStringFromClass([BNRAppDelegate class]));
}
}


The function UIApplicationMain creates an instance of a class called UIApplication. For every
application, there is a single UIApplication instance. This object is responsible for maintaining the
run loop. Once the application object is created, its run loop essentially becomes an infinite loop: the
executing thread will never return to main().
每个应用都会创建唯一的UIApplication实例,它负责维护事件循环。当这个对象创建之后,就会有启动一个无限循环
(类似于for、while); 这个运行的线程永远也回到main方法中运行了。


Another thing the function UIApplicationMain does is create an instance of the class that will serve
as the UIApplication’s delegate. Notice that the final argument to the UIApplicationMain function
is an NSString that is the name of the delegate’s class. So, this function will create an instance of
BNRAppDelegate and set it as the delegate of the UIApplication object.
UIApplicationMain方法中的另外一个功能是指定UIApplication对象的代理。


The first event added to the run loop in every application is a special “kick-off”
event that triggers the application to send a message to its delegate. This message is
application:didFinishLaunchingWithOptions:. You implemented this method in
BNRAppDelegate.m to create the window and the controller objects used in this application.
程序的第一个消息时发送给delegate的"kick-off(启动)",代理会运行application:didFinishLaunchingWithOptions:. 
方法;我们在这里会创建一个window,并创建在应用中使用的controller.


Every iOS application follows this pattern. If you are still curious, go back and check the main.m file in
the Quiz application that you wrote in Chapter 1.
每个ios应用几乎都是这种模式,如果你还不理解,可以返回到之前的章节查看。


Silver Challenge: Pinch to Zoom
Add pinch-to-zoom to the Hypnosister project from Chapter 5.


The first step is to give the scroll view a delegate:
• BNRAppDelegate should conform to the UIScrollViewDelegate protocol.
• In application:didFinishLaunchingWithOptions:, set the scroll view’s delegate property.
首先要给scroll view设置delegate:
1,在BNRAppDelegate中实现UIScrollViewDelegate协议
2,在application:didFinishLaunchingWithOptions:, 中设置scroll view的delegate为BNRAppDelegate


To perform as the scroll view’s delegate, BNRAppDelegate will need a property that points to the
instance of BNRHypnosisView. Add this property in a class extension in BNRAppDelegate.m and update
the rest of the code to use the property instead of the BNRHypnosisView local variable.
作为scroll view的delegate,BNRAppDelegate需要提供一个property指向BNRHypnosisView的实例。
在BNRAppDelegate.m的类扩展中添加属性,并将代码中的BNRHypnosisView局部变量改为属性。


To set up the scroll view, you will need to give it one BNRHypnosisView as a subview and turn off
the paging. The scroll view also needs limits on how much it can zoom in and out. Find the relevant
UIScrollView properties to set in this class’s reference page in the documentation.
为了建立scroll view,你需要将BNRHypnosisView加到scroll view中,并关闭分页机制。scroll view也需要
限制最大和最小的zoom。在UIScrollView的相关文档中找到相关的参数。


Finally, you will need to implement the scroll view delegate method viewForZoomingInScrollView:
to return the BNRHypnosisView.
最后你需要在代理中实现viewForZoomingInScrollView:,并传回BNRHypnosisView作为参数


If you get stuck, visit the reference pages for the UIScrollView class and for the
UIScrollViewDelegate protocol.


To simulate two fingers in the simulator to test your zooming, hold down the Option key while using
the mouse.
为了模拟zoom操作,你要在使用鼠标的时候按住Option键。


Big Nerd Ranch Guild

你可能感兴趣的:(第七章 Delegation and Text Input)