Feb 12

【Lrdcq的iOS入门 DAY2】简单的界面交互响应

Lrdcq , 2016/02/12 20:42 , 教程 , 閱讀(2948) , Via 本站原創
Attention:本教程,不,其实是我学习review笔记,主要目的是我自己将我近一个月以来学习ios的姿势与方式用循序渐进的方式重新整理与归纳,并总结为阶梯性的知识点。教程意义不大,多半是给自己看的,所以看到我胡扯乱弹琴或者老是忘事儿请不要太在意。
本教程所有第0章节都是废话,有基础者可以跳过的,请务必注意。


iOS控件组成 - 来一个按钮 - 再来一个文本框 - 从UIView内部攻破



step0.iOS控件组成

上一个教程我们用到了UILabel,当然,显然还有很多很多UI开头的类,这些是ios内置的UIKit的界面相关的类,当然其中包括我们能用到的所有自带控件了。那ios的控件是怎么构成的呢?

我们首先可以直观的看到的就是所有控件的基类UIView了,和安卓里的view一样,UIView是一个什么都没有的控件。不过它更像是一个viewgroup,也就是说ios中所有控件都可以添加子view,着算是和安卓控件体系最大的不同了,对应的iso中也没有叫啥啥layout的空间,全都是绝对布局或者自动布局咯。从另外一个角度来说,每一个UIView都是我们的画布,我们可以在上面添加/绘制任意的东西,比起安卓的view起来自由很多。另外同安卓的view一样,它可以处理大量的触控事件。(UIView包含绘制CALayer和交互UIResponder两部分,参看http://www.cocoachina.com/ios/20150828/13244.html)

在UIView,其实我们常用的控件并不是很多,静态控件,文本UILabel,图片UIImageView,万能的UITableView(类比与listview);交互控件,按钮UIButton,文本框UITextField,开关UISwitch等,基本控件组合一下就可以完成复杂的界面了。

step1.来一个按钮

那么,我们回到helloworld,我们在界面上添加如下的UIButton吧:
[self.view addSubview:({
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    btn.frame = CGRectMake(self.view.frame.size.width/2-50, self.view.frame.size.height/2-30, 100, 60);
    [btn setTitle:@"按钮" forState:UIControlStateNormal];
    [btn setTitle:@"按下" forState:(UIControlStateHighlighted|UIControlStateSelected)];
    btn;
})];

控件初始化的方式有一点不一样,我们使用了可以调出系统自带的样式的静态方法吐出了button实例,再设置了frame。然后,我们通过setTitle方法设置按钮上的文本。setTitle方法有第二部分forState的状态mask,望文生义,我们在按钮的普通状态设置了“按钮”文本,而在按钮的高亮与选中状态设置了“按下”文本。可以设置状态的主要方法还有并不限于:
- (void)setTitle:(nullable NSString *)title forState:(UIControlState)state;//文本
- (void)setTitleColor:(nullable UIColor *)color forState:(UIControlState)state;//文本颜色
- (void)setTitleShadowColor:(nullable UIColor *)color forState:(UIControlState)state;//文本阴影颜色
- (void)setImage:(nullable UIImage *)image forState:(UIControlState)state;//按钮图标
- (void)setBackgroundImage:(nullable UIImage *)image forState:(UIControlState)state;//按钮背景图像
- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(UIControlState)state;//详细的文本样式

同时,UIControlState的mask包含常用状态:
UIControlStateNormal//正常状态
UIControlStateHighlighted//高亮状态
UIControlStateDisabled//禁用状态
UIControlStateSelected//选中状态
UIControlStateFocused//获得焦点状态

对于一般的UIButton来说,Normal,Highlighted,Disabled是有意义的。通过这几对方法,我们可以简单有效的设置按钮在不同状态下的基本样式了,简直是方便。当然作为程序逻辑,我们自己的状态机可不止这么几个状态,这个就需要我们自己监听并处理事件了。

在UIView下,所有需要处理交互的控件都继承于UIControl控件,它提供了控件完整的控制事件接口封装,比如对于不同控件启用禁用,是否可选之类的(这也涉及到上面状态设置的改变),当然最重要的是设置事件响应了。为我们的按钮添加一个典型的触控事件是这样的:
//定义处理按键事件的方法
- (void)dealButtonAction{
    NSLog(@"do something");
}
//再把它绑定到按钮上
[btn addTarget:self action:@selector(dealButtonAction) forControlEvents:UIControlEventTouchUpInside];

可以看到UIControl提供的addTarget方法,它的意义是一个目标对象(Target)的一个方法(action),响应一个事件交互(ControlEvents)。在这里,我们是把处理方法写在自己(vc)的一个方法中的,因此是self的@selector(dealButtonAction)(@selector可以理解为用方法名将方法转换为函数指针)。ControlEvents的mask也是一个很长的列表,包括所有的触控/拖曳/编辑事件,也就是一个大杂烩,适用于UIButton的事件mask主要包括:
UIControlEventTouchDown//所有的按下都会触发
UIControlEventTouchDownRepeat//多次点击
UIControlEventTouchDragInside//
UIControlEventTouchDragOutside//
UIControlEventTouchDragEnter//
UIControlEventTouchDragExit//四个拖曳响应
UIControlEventTouchUpInside//
UIControlEventTouchUpOutside//在按钮内/外弹起
UIControlEventTouchCancel//按下取消,适用于特殊情况(比如触控事件被强行中断)

显然,作为一般的按钮,显然UIControlEventTouchUpInside就非常恰当了。另外,响应的方法还可以接受参数:(id)sender,sender返回的应该是发起事件的控件实例,响应方法也可以返回(IBAction)来辅助xcode...没什么卵用。

总之这样,一个完整的按钮处理与反馈的环就完成了。这是ios控件中一个标准的Target-action模式实践(参看教程http://www.cnblogs.com/wzrong/p/3218867.html)。

step2.再来一个文本框

除了按钮,更加常用的交互控件是文本输入框。文本输入框其实有两个控件,一个是UITextField即单行文本,UITextView是一个文本域即多行文本。两者的api差别并不大,主要区别是应用场景不同而已。那么相对更常用的,我们加一个UITextField:
[self.view addSubview:({
    UITextField *text = [[UITextField alloc] initWithFrame:CGRectMake(self.view.frame.size.width/2-150, self.view.frame.size.height/2-30, 300, 60)];
    text.borderStyle = UITextBorderStyleRoundedRect;
    text.placeholder = @"How much is it?";
    text.clearButtonMode = UITextFieldViewModeAlways;
    text.keyboardType = UIKeyboardTypeWebSearch;
    text.returnKeyType = UIReturnKeyGo;
    text;
})];

在UITextField创建的时候,除了一般的文本/背景样式设置,我们也要进行一些特殊的设置。包括自带的边框样式(否则什么也没有),背景提示文本(一般都会设置),清除按钮的显示方式,键盘类型(数字键盘还是其他别的),键盘确认键内容(与图标)。基本的文本框设置即这些,接着,我们尝试监听文本内容的改变,模仿处理按钮事件改变的方式,代码如下:
[text addTarget:self action:@selector(dealTextAction:) forControlEvents:UIControlEventEditingChanged];
- (void)dealTextAction:(UITextField*)sender{
    //do something
    NSLog(sender.text);
}

这样,每次编辑文本框中的文本之后,事件就会响应,做出响应的操作(打log)。与文本框相关的事件包括如下:
UIControlEventEditingDidBegin//开始点进入文本框时编辑
UIControlEventEditingChanged//文本改变时。常用
UIControlEventEditingDidEnd//焦点离开编辑框时
UIControlEventEditingDidEndOnExit//点击右下角的确认键时触发

通过这四个事件,我们基本上可以处理文本编辑框的所有基本事件了。同时,另外一个api,文本框提供了一个代理接口UITextFieldDelegate,这个接口提供了如下方法主要包括:
- (void)textFieldDidBeginEditing:(UITextField *)textField;//限制开始编辑时触
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField;//限制离开文本编辑
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;//每次改变文字时触发,用于限制编辑文本,最主要的一个操作
- (BOOL)textFieldShouldClear:(UITextField *)textField;//限制清除操作
- (BOOL)textFieldShouldReturn:(UITextField *)textField;//用于限制点击确认键

这一堆方法与其所时对文本操作的监听,不如说是对实际操作中的业务需求限制提供便捷的入口,相对于用Target-action的方式,更具有实际业务意义。比如常见的实际业务中我需要限制这个文本框的文本长度最大为10,我会添加如下代码:
text.delegate = self;
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
    if ([textField.text length]+[string length]-range.length > 10){
        return NO;
    }
    return YES;
}

每当文本编辑时触发,如果编辑后的文本长度大于了限制的长度10,则不允许编辑即可。

step3.从UIView内部攻破

当然,上面两个常用的交互控件,是通过它们继承的UIControl的方法来处理的,但是难道不是继承UIControl的view就不能处理事件了么,显然不是。针对自己继承下来的UIView的之类,我们主要是重写来自父类留下的这四个接口:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

通过重写这四个方法,我们基本可以处理所有的view触控事件(当然,参看http://southpeak.github.io/blog/2015/03/07/cocoa-uikit-uiresponder/)。另外,UIResponder也提供了一套较为底层的手势识别事件的监听接口,通过这个接口可以在所有UIView上响应触控事件。假如我要用这一套接口处理按钮点击事件,我们会这样写:
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dealButtonAction)];
[btn addGestureRecognizer:singleFingerTap];

同样dealButtonAction也可以接收一个参数(UITapGestureRecognizer *)recognizer,从这个识别对象中我们可以找到相关事件坐标等信息。addGestureRecognizer可以接受的UIGestureRecognizer很多,主要包括:

  - UITapGestureRecognizer 点击
  - UIPinchGestureRecognizer 双指缩放
  - UIRotationGestureRecognizer 双指旋转
  - UISwipeGestureRecognizer 滑动
  - UIPanGestureRecognizer 拖移
  - UILongPressGestureRecognizer 长按
  
用这一套api,可以简单的完成苹果常见的多点触控手势场景识别,这也是于UIControl的交互控制最大的不同。具体使用可以参看:http://blog.csdn.net/likendsl/article/details/7554150

可以看到,ios中基本的控件交互响应主要通过,Target-Action,Delegate,GestureRecognizer三种方式来实现的,总结来看,继承于UIControl的控件多半是Target-Action,操作较为复杂的控件(比如下次讲的TableView)多半可以设置Delegate,所有控件需要识别复杂的手势可以使用GestureRecognizer。
logo