iOS Programming 7 (Delegation and Text Input)

UIResponder

这一章主要讲解了iOS中的delegation.为了使这个概念更加清楚,我们通过一个例子来讲解.在上一章代码的基础上,我们在UITabBarController的第一个viewcontroller上加一个UITextField,这个textfield允许用户输入. 在创建完UITextField之后,我们把它加到viewcontroller的view中去:

1
2
3
4
5
6
7
8
9
10
- (void)loadView {
    BNRHypnosisterView *backgroundView = [[BNRHypnosisterView alloc] init];

    CGRect rect = CGRectMake(40, 70, 240, 30);
    UITextField *textField = [[UITextField alloc] initWithFrame:rect];

    [backgroundView addSubview:textField];

    self.view = backgroundView;
}

运行程序,可以看到屏幕中间有一个输入框:

当点击输入框的时候,键盘会自动跳出来,为了理解这个动作,我们首先需要理解一个叫做first responder的原理.

在UIKit framework中有一个抽象类UIResponder,他是UIView,UIViewController,UIApplication的共同父类.在这个抽象类中定义了许多事件,比如touch event, motion events.由继承这个抽象类的子类选择去实现.在UIWindow中,有一个叫做firstResponder的指针,被它指向的对象用来处理这些事件.

当用户点击UITextField的时候,这个firstResponder就指向了UITextField,代表这个UITextField变成了一个first responder,这时键盘就会弹出来,当firstResponder不在指向它的时候键盘就退回去.

Delegation && Protocol

在Delegate pattern中主要有object和delegate,它们的关系如下:

object会有一个指针指向delegate,在object需要做一些事之前或者做完一些事之后会发送消息给delegate,然后delegate再完成一些其他的操作.例如,UITextField在用户输入完成后需要隐藏键盘,这时UITextField会给viewcontroller发送消息,案后这个viewcontroller可以完成隐藏键盘的操作.为了让WUSTLHypnosisViewController成为UITextField的delegate,我们需要加上下面这条语句:

1
textField.delegate = self;

因为这句话是在WUSTLHypnosisViewController里面,所以self指的是viewcontroller.这里有一个疑问,我们应该基于什么样的消息机制建立object和delegate之间的通信呢,这里就需要引出另外一个概念 – protocol. Protocol定义了object能发送给delegate的消息,delegate负责实现protocol中的一些方法, 如果一个类实现了Protocol中的一些方法,我们称之为遵守协议.下面是一个Protocol的示例:

1
2
3
4
5
6
7
@protocol UITextFieldDelegate <NSObject>

@optional
- (BOOL) textFieldShouldBeginEditing:(UITextField *)textField;
- (BOOL) textFieldDidBeginEditing:(UITextField *)textField;
- (BOOL) textFieldShouldEndEditing:(UITextField *)textField;
- (BOOL) textFieldShouldClear:(UITextField *)textField;

上面的代码定义了一个Protocol,它其实类似于Java中的interface,都是只声明方法签名,留给子类去实现.它们分别在不同的时候被自动触发,例如textFieldShouldBeginEditing会在用户选中输入框的时候触发.上面的意思是遵守NSObject的协议,即这个Protocol中包含了所有NSObject Protocol中定义的方法.@optional代表下面的方法是可选实现的,非强制.当object尝试给delegate发送消息之前,它会发送另一个消息respondToSelector确认delegate是否实现了相应的方法.

下面我们来实现一个Protocol的方法,在这个方法里面我们调用另一个方法把用户输入随机打印20遍在屏幕上,然后清空输入框并且隐藏键盘.

1
2
3
4
5
6
- (BOOL)textFieldShouldReturn:(UITextField *) textField {
    [self drawHypnoticMessage:textField.text];
    textField.text = @"";
    [textField resignFirstResponder];
    return YES;
}

这个方法在用户输入完成点击键盘上Return时候被触发.倒数第二句话是用来通过设置UITextField不为firstResponder来达到隐藏键盘的目的.

下面是随机打印的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void) drawHypnoticMessage:(NSString *) message {
    for (int i = 0; i < 20; i++) {
        UILabel *messageLabel = [[UILabel alloc] init];
        messageLabel.backgroundColor = [UIColor clearColor];
        messageLabel.textColor = [UIColor redColor];
        messageLabel.text = message;

        [messageLabel sizeToFit];

        int width = (int) (self.view.bounds.size.width - messageLabel.bounds.size.width);
        int x = arc4random() % width;

        int height = (int) (self.view.bounds.size.height - messageLabel.bounds.size.height);
        int y = arc4random() % height;

        CGRect frame = messageLabel.frame;
        frame.origin = CGPointMake(x, y);
        messageLabel.frame = frame;

        [self.view addSubview:messageLabel];

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

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