UI第四天---文本框和视图控制器


##UITextField控件##
###UITextFiled常用属性和方法###
&emsp;&emsp;UITextField是常用的文本输入控件，比如我们用的QQ的登录界面，词典输入要查询的单词都使用了文本框控件，如下图所示。之前介绍的UILabel可以在界面中显示文本，但用户无法选择或编辑UILabel中的文本，想接受用户输入文本，就可以使用UITextField控件。当我们在用户界面上点击文本框时，屏幕底部会弹出键盘，用于向文本框中输入文字。



>**- borderStyle**：边框样式（UITextBorderStyleNone, UITextBorderStyleBezel, UITextBorderStyleLine, UITextBorderStyleRoundedRect）<br/>
>**- background**：背景图<br/>
>**- backgroundColor**：背景色<br/>
>**- placeHolder**：没有输入内容时的占位符<br/>
>**- text**：文字<br/>
>**- textColor**：文字颜色<br/>
>**- textAligment**：对齐方式<br/>
>**- font**：字体<br/>
>**- enabled**：是否可编辑<br/>
>**- secureTextEntry**：是否使用掩码<br/>
>**- adjustsFontSizeToFitWidth**：是否调整字体大小适应宽度<br/>
>**- leftView / leftViewMode**：左视图（可以定制文本框）/ 左视图模式<br/>
>**- rightView / rightViewMode**：右视图（可以定制文本框，可能会覆盖掉清除文字的按钮）/ 右视图模式<br/>
>**- inputView / inputAccessoryView**：输入/辅助视图（可以完成键盘的定制，可以在原始的键盘上设置二级键盘）<br/>

###如何定制键盘###

>**- keyBoardAppearance**：键盘的外观(四种外观，分别是：UIKeyboardAppearanceDefault、UIKeyboardAppearanceLight、UIKeyboardAppearanceDark、UIKeyboardAppearanceAlert)<br/>
>**- keyBoardType**：键盘的类型（UIKeyboardTypeAlphabet、UIKeyboardTypeASCIICapable、UIKeyboardTypeDecimalPad、UIKeyboardTypeDefault、UIKeyboardTypeEmailAddress、UIKeyboardTypeNamePhonePad、UIKeyboardTypeNumberPad、UIKeyboardTypeNumbersAndPunctuation、UIKeyboardTypePhonePad，默认键盘可以输入其他键盘输入的内容）<br/>
>**- returnKeyType**：返回键的类型（UIReturnKeyDefault、UIReturnKeyDone、UIReturnKeyEmergencyCall、UIReturnKeyGo、UIReturnKeyGoogle、UIReturnKeyJoin、UIReturnKeyNext、UIReturnKeyRoute）<br/>

###第一响应者###
&emsp;&emsp;UIResponder是UIKit框架中的一个抽象类，UIView、UIViewController、UIApplication都是它的子类。UIResponder定义了一系列方法，用于接收和处理用户事件，例如触摸事件、运动事件（摇晃手机）和功能控制事件（编辑文本或播放音乐）等。以上事件中，触摸事件显然应该由被触摸的视图负责处理，系统将触摸事件直接发送给被触摸的视图。其他类型的事件则会由第一响应者（first responder）负责处理，UIWindow有一个firstResponder属性指向第一响应者。如果希望启动应用程序时文本框直接获得焦点显示输入键盘，或者希望点击屏幕其他区域输入键盘消失可以通过下面的方法来设置第一响应者来实现。

>**- becomeFirstResponder方法**<br/>
>**- resignFirstResponder方法**<br/>


###UITextField常用代理方法###
&emsp;&emsp;在之前的OC课程中，我们了学习过一个非常重要的概念叫协议（相当于Java和C#中的接口），它是实现委托回调的关键，如果一个类遵守这个协议，那么它拥有协议中规定的方法，我们就可以将它的对象设置为被委托放，来帮助委托方完成某些功能。如果一个类遵循UITextFieldDelegate协议，它的对象就可以作为被委托方帮我们处理文本框（委托方）编辑时引发的事件。

>**- delegate属性**

&emsp;&emsp;UITextFieldDelegate协议的源代码：

```Objective-C
@protocol UITextFieldDelegate <NSObject>

@optional
// 可以通过返回NO阻止编辑
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField;

// 当文本框成为第一响应者时执行此方法
- (void)textFieldDidBeginEditing:(UITextField *)textField;

// 返回YES来允许结束编辑
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField;

// 当文本框放弃第一响应者时执行此方法
- (void)textFieldEndEndEditing:(UITextField *)textField;

// 返回NO可以阻止对文本框内容的修改（用于定制文本框输入内容）
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string

// 清除文本框中的文本时执行
- (BOOL)textFieldShouldClear:(UITextField *)textField;

// 输入结束返回时执行
- (BOOL)textFieldShouldReturn:(UITextField *)textField;

@end
```

&emsp;&emsp;要实现在输入完成时关闭键盘，可以重写UITextFieldDelegate协议中的textFieldShouldReturn:方法。如果希望点击应用程序其他部分时键盘消失可以重写UIResponder中的touchesEnded:withEvent:方法。

&emsp;&emsp;我们用一个完整的例子把刚才的知识点串联一下，App的运行效果和代码如下所示。

运行效果：



```Objective-C

```

##UIViewController视图控制器##

###创建视图控制器###

&emsp;&emsp;视图控制器是UIViewController类或其子类对象，每个视图控制器都负责管理一个视图层次结构，包含创建视图层次结构中的视图并处理相关用户事件，以及将整个视图层次结构添加到应用窗口。

&emsp;&emsp;MVC架构模式：模型-视图-控制器，UIViewController就是MVC模式中的C。

&emsp;&emsp;视图控制器可以通过两种方式创建视图层次结构：
>**- 代码方式**：重写UIViewController中的loadView方法<br/>
>**- xib方式**：<br/>
>**- storyboard方式**：<br/>

###视图控制器的生命周期###
![这里写图片描述](http://img.blog.csdn.net/20150701104315699)

>说明：loadView和viewDidLoad的区别就是，loadView执行时view还没有生成，viewDidLoad时，view已经生成了，loadView和viewDidLoad通常只会被调用一次，而其他方法可能会被调用多次，当view被添加到其他view中之前，会调用viewWillAppear，之后会调用viewDidAppear。当view从其他view中移除之前，调用viewWillDisAppear，移除之后会调用viewDidDisappear。当view不再使用或受到内存警告时，ViewController可能会将view释放并将其指向为nil。图中的viewDidUnload方法在iOS 6中已经过时，可以在下面的方法中处理收到内存警告时要做的事情。

###内存告急时会发生什么###
>回调UIViewController对象的didReceiveMemoryWarning方法。

###UIViewController的视图切换###
- 通过模态化的方式切换视图

    - **presentViewController:animated:completion:**显示模态视图控制器
    - **dismissViewControllerAnimated:completion:**撤回模态视图控制器

- 直接切换视图

    - 通过UIView的window属性获得UIWindow对象
    - 重新指定UIWindow对象的rootViewController属性

- 通过管理子视图的方式切换视图

&emsp;&emsp;用一个视图控制器做容器，将其他的视图控制器加入到容器视图控制器中，再将第一个要呈现的视图控制器的视图贴到容器视图控制器上（[self.view addSubiew:self.childViewControllers[0]]）。当需要进行视图切换的时候，可以调用容器视图控制器的transitionFromViewController:toViewController:duration:options:animations:completion方法；如果要从容器视图控制器中移除当前视图控制器可以调用removeFromParentViewController方法。

###UIViewController旋转###
- 设定旋转方向

    - plist文件设置：&quot;Supported interface orientations&quot;
    - 程序中的设置，需要重写视图控制器的以下方法：
        - **shouldAutoRotate**：是否自动旋转，如果设置为NO就需要通过代码来完成视图控制器的旋转。
        - **supportedInterfaceOrientations**：支持的旋转方向，可以使用枚举值进行指定，如果需要支持四个方向的转动，可以返回UIInterfaceOrientationMaskAll。


- 通过代码来旋转

&emsp;&emsp;有时候旋转屏幕之后需要对整个内容做出调整，因此我们需要监听视图控制器的旋转。可以通过创建一个通知中心来观察当前设备发生方向的旋转，然后绑定处理旋转事件的回调方法，在回调方法中通过旋转的方向来得到旋转的角度，然后通过视图的transform属性对视图进行变换。代码如下所示：

&emsp;&emsp;创建通知中心并添加观察者的代码。原来视图控制器中可以监听旋转的两个方法（willRotateToInterfaceOrientation:duration和didRotateFromInterfaceOrientation）在iOS 8中已经废除了。

```Objective-C
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(didRotate) name:UIDeviceOrientationDidChangeNotification object:nil];

```
&emsp;&emsp;回调方法的代码。

```Objective-C
- (void) didRotate {
    UIDeviceOrientation orient = [UIDevice currentDevice].orientation;
    CGFloat delta = 0;
    switch(orient) {
        case UIInterfaceOrientationPortrait:
            delta = 0;
            break;
        case UIInterfaceOrientationLandscapeRight:
            delta = M_PI_2;
            break;
        case UIInterfaceOrientationLandscapeLeft:
            delta = -M_PI_2;
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            delta = M_PI;
            break;
        default:
            delta = 0;
    }
    // NSLog(@"%f", delta);
    CGAffineTransform trans = CGAffineTransformMakeRotation(delta);
    self.view.transform = trans;
}
```

###视图控制器之间传值###

- 及时性传值

(1) 委托传值：提供数据（状态）的视图控制器通过委托回调需要数据（状态）的控制器中的方法。

(2) block传值：需要数据的视图控制器通过块将一段代码传入提供数据的视图控制器中。

创建block

```Objective-C
- (void) okButtonClicked {
    void (^fn)(UISwitch *) = ^(UISwitch * bt) {
        UIButton *okButton = (id)[self.view viewWithTag:101];
        [okButton setTitle:(bt.isOn? @"关闭蓝牙":@"打开蓝牙") forState:UIControlStateNormal];
        self.flag = bt.isOn;
    };	// 创建一个块

	// ... ...

    CDModalViewController *modalVC = [[CDModalViewController alloc]init];
    modalVC.fn = fn;	// 将块传输模态视图控制器

    [self presentViewController:modalVC animated:YES completion:nil];
}
```

调用block

```Objective-C
- (void) switchValueChanged:(UISwitch *)bt {
    void (^block)(UISwitch *) = self.fn;
    block(bt);
}
```

(3) 通知传值：需要数据的视图控制器注册一个观察者，提供数据的视图控制器向观察者发送通过，这是对观察者模式的应用。

通过消息中心注册观察者

```Objective-C
- (void)viewDidLoad {
    [super viewDidLoad];

	// ... ...
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showSwitchStatus:) name:@"SwitchValueChanged" object:nil];
}

- (void) showSwitchStatus:(NSNotification *) not {
    UIButton *okButton = (id)[self.view viewWithTag:101];
    UISwitch *currentSwitch = not.object;
    self.flag = currentSwitch.isOn;
    [okButton setTitle:(self.flag? @"关闭蓝牙":@"打开蓝牙") forState:UIControlStateNormal];
}
```

在事件发生时向观察者发出通知

```Objective-c
- (void) switchValueChanged:(UISwitch *)bt {
    NSNotification *not = [[NSNotification alloc] initWithName:@"SwitchValueChanged" object:bt userInfo:nil];
    [[NSNotificationCenter defaultCenter] postNotification:not];
}
```

- 非及时性传值

AppDelegate传值和单例模式



>**补充：**单例模式是一种常用的设计模式，它让一个类只能创建出唯一的对象，很多应用中都需要这种独一无二的对象，我们也可以将单例对象作为共享状态的对象来实现传值。实现代理模式的关键是不提供公开的初始化方法（构造方法），通过一个类方法向外界返回该类的唯一实例。


##综合练习##