iOS控件之UIResponder类

iOS控件之UIResponder类

在iOS中UIResponder类是专门用来响应用户的操作处理各种事件的,我们知道UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder,UIWindow是直接继承自UIView的一个特殊的View,所以这些类都可以响应事件。当然我们自定义的继承自UIView的View以及自定义的继承自UIViewController的控制器都可以响应事件。iOS里面通常将这些能响应事件的对象称之为响应者。

iOS中事件Event可以简单划分为两种,一种是系统事件(如来电、内存警告等),另外的我们就称非系统事件(触摸事件、运动事件、远程控制事件)。

无论是系统事件还是非系统事件,iOS系统都是最先知道事件的,然后将事件加入到当前正在运行的app的UIApplication的事件队列中。UIApplication会按照先进先出的顺序从事件队列中取出事件,然后分发出去寻求响应。

UIApplication对接收到的系统事件会分发给它的代理AppDelegate对象进行处理,其他的非系统事件会传给UIApplication的keyWindow去寻找合适的view进行响应。

本文主要讨论非系统事件,包括触摸事件(Touch Events)、运动事件(Motion Events)、远程控制事件(Remote Control Events,如插入耳机调节音量触发的事件)。关于系统事件可以参考这篇文章http://www.cnblogs.com/shaoting/p/5029092.html

下面我们根据UIResponder.h头文件来具体介绍关于响应事件的各个方面。

这个部分我们主要介绍3种事件类型,即触摸事件,运动事件,远程控制事件。当用户触发某一事件时,UIKit会创建一个UIEvent事件对象(关于iOS事件对象可以参考这篇文章),事件对象会加入到一个FIFO先进先出的队列中,UIApplication对象处理事件时,会从队列头部取出一个事件对象进行分发。

1.触摸事件

@interface UIResponder : NSObject
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;//触摸屏幕
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;//在屏幕上移动
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;//离开屏幕
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;//系统事件干扰

这4个方法是触摸事件的最原始处理的4个方法,分别代表触摸屏幕,在屏幕上移动,离开屏幕以及受到系统优先级别高的事件的干扰(比如来电话)取消触摸事件。另外UIKit框架对于触摸事件为我们提供了UIGestureRecognizer手势识别这个类,基本上能满足我们的大部分需求(可以参考这篇文章)。这里介绍的是最底层的处理方法,比如可以用来实现绘图类型的APP.
上面提到UIApplication对象从队列中取出事件对象进行分发,对于触摸事件来说,UIApplication会首先把事件交给keyWindow,Window会将事件交给UIGestureRecognizer处理,如果UIGestureRecognizer识别了传递过来的事件,则交给相对应的target去处理(关于iOS手势事件可以参考这篇文章),事件不会再传递,如果UIGestureRecognizer并没有识别传递过来的事件(可能是没有视图添加手势,也可能手势识别不成功),事件会传递到视图树形结构,会分成寻找接受者和事件响应这两个步骤。
1.在iOS视图树形结构中找到最终的接收者,也就是触摸事件发生的那个最上层的View上,这一过程称为hit-testing(测试命中),通过一层层的遍历找到最终的命中视图称为hit-test view.
UIView中有两个方法用来确定hit-test view.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

这是一张我从apple官方文档里面的截图,这里所有的显示的View都是加载到主window上,假设我们触摸到屏幕上ViewD的区域,当我们没有重载UIView的hitTest:withEvent:pointInside:withEvent:这两个方法时,系统默认的处理如下:

  • keyWindow调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes,遍历keyWindow的subView->ViewA.
  • ViewA调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes,遍历ViewA的subView->ViewB、ViewC(关于ViewB和ViewC先执行哪个,是根据ViewA添加子控件的先后顺序,总是先执行后添加的subView.假设添加ViewB后添加ViewC)
  • ViewC调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes,遍历ViewC的subView->ViewD、ViewE
  • ViewE调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回NO,ViewE的hitTest:withEvent:返回nil(如果是先执行ViewB的情况,假设ViewB还有子节点subView,由于ViewB的pointInside:withEvent:返回NO,ViewB的hitTest:withEvent:`直接返回nil是不会再去遍历ViewB的子节点的)
  • ViewD调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes并且没有子节点subView,ViewD的hitTest:withEvent:返回ViewD本身,即为最终的hit-test view(不会再遍历ViewB)iewB

    需要注意的是:View.isHidden=YES View.alpha<=0.01 View.userInterfaceEnable=NO View.enable = NO(指继承自UIControl的View)的这4种情况下,View的pointInside返回NO,hitTest方法返回nil
    默认UIImageViewuserInterfaceEnable=NO

2.找到了hit-test view,下一个步骤就是响应事件。说明一下,对于触摸事件来说,无论View是否处理事件,即使是application通过[application beginIgnoringInteractionEvents]忽略了触摸事件,上面hit-testing的过程依然存在,它只影响第二个步骤事件响应的过程。下面我们将介绍iOS响应者链条(Responder chain)

这是我从官方文档里面截取的一张关于响应者链条的截图。我们先看上图左边的情况:标注为①的地方即为步骤1找到的hit-test view 它作为第一响应者来响应这个事件,如果该view没有通过重写或者封装touch系列方法来处理该事件,默认touch的实现就是调用父类的touch方法,将事件传递下去。在这里由1->传递到它的父类2,2是控制器的根view,->传递到vc控制器->传递到窗口window->传递到application

再看上图右边的情况:标注为①的地方即为步骤1找到的hit-test view,同时它是控制器的根view并且还有父视图,事件传递到控制器->再传递到父视->传递到控制器,再传递到父视图窗口->application。其实上图左边部分也可以理解为窗口是控制器根视图的父视图。如果整个响应者链条结束,都没有对事件做处理,那么该事件会被丢弃。

总结一下响应者链条的传递过程是:由第一响应者(对于触摸事件来说是hist-test view)开始向上传递。如果该视图是控制器的根视图,先传递给控制器,再传递给父视图,如果不是控制器的根视图,直接传递给父视图。
只要在响应者的处理方法里面调用父类的方法,就可以让多个视图和控制器响应同一个事件,响应者链条的根本目的是:共享事件,让多个视图和控制器可以对同一事件做不同的处理。

2.运动事件

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);

这3个方法是运动事件的最原始处理的3个方法,这里处理的运动事件特指shake事件,手机摇动触发手机内部的加速度传感器,可以用来实现摇一摇计算运动的步数等等应用。类似于触摸事件,这3个方法分别代表事件开始、事件结束和受到系统干扰取消事件。
加速度计accelerometer实际上是由三个加速度计组成,分别用于测量X,Y和Z轴直线路径速度的变化。结合所有三个加速度计可以检测设备朝任何方向的运动和获取设备的当前方向。对于shake事件来说,我们不关心3个方向上的运动,只作为一个事件对象来处理。如果只是处理设备的大方向,并不需要知道方向向量,如横屏竖屏屏幕旋转,我们可以使用UIDevice类(参考文章)。如果我们需要知道3个方向上的运动做更细致化的处理,如上了多少层楼等运动类型APP,可以使用的核心运动框架访问加速度计,陀螺仪和设备的运动类来做处理(Core Motion参考文章)
上面介绍的响应者链条对shake事件同样适用,只不过,没有hit-testing过程,如果当前显示的视图界面没有一个view声明为第一响应者(调用becomeFirstResponder申明并且View需要重写canBecomeFirstResponder方法返回YES,默认返回为NO),默认当前视图控制器为第一响应者,并将事件沿着响应者链条传递,直到被处理。如果有视图声明为第一响应者,就从该视图开始传递事件直到被处理,如果该事件最终没有被处理并且UIApplication的applicationSupportsShakeToEdit属性为YES(默认就是YES),当键盘显示的时候,系统会有一个是否撤销正在输入的警告。就是微信和QQ上在输入的时候摇动手机提示撤销输入的那种效果。关于更多撤销方面的操作参考NSUndoManager

3.远程控制事件(本文暂不讨论)

4.UI控件不能响应事件的几种情况

注意:UIView.enable = NO;时也不能响应事件!

以上内容大多转自http://www.jianshu.com/p/2dda99a0e09a

时间: 2024-10-12 12:17:23

iOS控件之UIResponder类的相关文章

设计一个 iOS 控件

代码的等级:可编译.可运行.可测试.可读.可维护.可复用 前言 一个控件从外在特征来说,主要是封装这几点: 交互方式 显示样式 数据使用 对外在特征的封装,能让我们在多种环境下达到 PM 对产品的要求,并且提到代码复用率,使维护工作保持在一个相对较小的范围内:而一个好的控件除了有对外一致的体验之外,还有其内在特征: 灵活性 低耦合 易拓展 易维护 通常特征之间需要做一些取舍,比如灵活性与耦合度,有时候接口越多越能适应各种环境,但是接口越少对外产生的依赖就越少,维护起来也更容易.通常一些前期看起来

Swift - 生成各种控件的工厂类(包含标签,按钮,输入框等)

在iOS开发中,页面里有时会大量的用到一些控件,如果要一个个单独创建再设置样式的话就显得很麻烦.我们可以创建一个生成各种控件的工厂类,这样在需要的时候调用下就可以了. 下面以一个自定义的工厂类为例,其中提供了文本标签,按钮,文本输入框,分段单选控件的生成,效果图如下: 工厂类:ViewFactory.swift 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 33 34

重新想象 Windows 8.1 Store Apps (77) - 控件增强: 文本类控件的增强, 部分控件增加了 Header 属性和 HeaderTemplate 属性, 部分控件增加了 PlaceholderText 属性

[源码下载] 重新想象 Windows 8.1 Store Apps (77) - 控件增强: 文本类控件的增强, 部分控件增加了 Header 属性和 HeaderTemplate 属性, 部分控件增加了 PlaceholderText 属性 作者:webabcd介绍重新想象 Windows 8.1 Store Apps 之控件增强 文本类控件的增强 为一些控件增加了 Header 属性和 HeaderTemplate 属性 为一些控件增加了 PlaceholderText 属性 示例1.演示

C#控件系列--文本类控件

C#控件系列--文本类控件 文本类控件主要包括Label.LinkLabel.Button.TextBox以及RichTextBox. Label 功能 Label用来显示用户不能直接改变的文本信息. 属性 Image--指定标签上显示的图像. Text--此属性是与文件相关联的文本. Name--标识对象的名称. AutoSize--如果此属性为true,则启用了根据字号自动调整大小.请注意,这只对文本不换行的标签控件有效. Enabled--是否启用该控件. Visible--可见还是隐藏.

从零开始学ios开发(六):IOS控件(3),Segmented Control、Switch

这次的学习还是基于上一个项目继续进行(你也可以新建一个项目)学习Segmented Control和Switch. Segmented Control Switch Segmented Control和Switch的主要区别在于Segmented Control可以有多个值进行选择,而Switch只有2个值. 1)添加Segmented Control从object library中拖一个Segmented Control到iphone界面上然后调整Segmented Control位置以及它的

MFC串口的编程 mscomm控件与SerialPort类

MFC制作上位机,首先需要了解的是串口的编程,一般有两种方法,一个是使用ActiveX控件,例如mscomm串口控件,还有一个是用SerialPort类或者一些其他的串口类,这两个的区别是使用SerialPort类不需要注册控件,在其他没有安装控件的电脑上也能够用. 一·使用mscomm串口控件 使用mscomm串口控件的方法网上一大堆,大致说一些方法和一些需要注意的地方.如果是使用VC6.0在WIN7上来编写就会有个问题会通常说添加控件的方法为选中项目à“工程”à“添加到工程”à“Compon

UIControl IOS控件编程 及UITextField的讲解

第一部分 UIKit提供了一组控件:UISwitch开关.UIButton按钮.UISegmentedControl分段控件.UISlider滑块.UITextField文本字段控件.UIPageControl分页控件. 控件是对UIView派生类的实用增强及补充,并可以直接附着于导航栏.表格单元,甚至更大的对象. 这些控件的基类均是UIControl,而UIControl派生自UIView类,所以每个控件都有很多视图的特性,包括附着于其他视图的能力.所有控件都拥有一套共同的属性和方法. 所以学

ios控件 UIControl

< UIControl> 1 处理用户事件的控件的基类,如UIButton,UISlider等 2 一般不直接实例化,而是使用他的子类 3 可以通过跟踪触摸事件来设置和获取控件状态,并且这些方法可以被子类继承 //添加一个事件 - (void)addTarget:(id)target action:(SEL)action forControlEvents: (UIControlEvents)controlEvents; //移除一个事件 - (void)removeTarget:(id)tar

C#控件系列--选择类控件

C#控件系列--选择类控件 选择类控件主要包括:ComboBox.CheckBox.CheckedListBox.RadioButton.NumericUpDown以及ListBox. ComboBox 功能 ComboBox控件用于在下拉组合框中显示数据,它结合了TextBox控件和ListBox控件的功能. 属性