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

 
 

iOS控件之UIResponder类的更多相关文章

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

    在iOS开发中,页面里有时会大量的用到一些控件,如果要一个个单独创建再设置样式的话就显得很麻烦.我们可以创建一个生成各种控件的工厂类,这样在需要的时候调用下就可以了. 下面以一个自定义的工厂类为例,其 ...

  2. 设计一个 iOS 控件

    转载自:http://blog.csdn.net/zhangao0086/article/details/45622875 代码的等级:可编译.可运行.可测试.可读.可维护.可复用 前言 一个控件从外 ...

  3. 背水一战 Windows 10 (34) - 控件(进度类): RangeBase, Slider, ProgressBar, ProgressRing

    [源码下载] 背水一战 Windows 10 (34) - 控件(进度类): RangeBase, Slider, ProgressBar, ProgressRing 作者:webabcd 介绍背水一 ...

  4. 背水一战 Windows 10 (33) - 控件(选择类): ListBox, RadioButton, CheckBox, ToggleSwitch

    [源码下载] 背水一战 Windows 10 (33) - 控件(选择类): ListBox, RadioButton, CheckBox, ToggleSwitch 作者:webabcd 介绍背水一 ...

  5. 背水一战 Windows 10 (32) - 控件(选择类): Selector, ComboBox

    [源码下载] 背水一战 Windows 10 (32) - 控件(选择类): Selector, ComboBox 作者:webabcd 介绍背水一战 Windows 10 之 控件(选择类) Sel ...

  6. 背水一战 Windows 10 (31) - 控件(按钮类): ButtonBase, Button, HyperlinkButton, RepeatButton, ToggleButton, AppBarButton, AppBarToggleButton

    [源码下载] 背水一战 Windows 10 (31) - 控件(按钮类): ButtonBase, Button, HyperlinkButton, RepeatButton, ToggleButt ...

  7. 背水一战 Windows 10 (30) - 控件(文本类): AutoSuggestBox

    [源码下载] 背水一战 Windows 10 (30) - 控件(文本类): AutoSuggestBox 作者:webabcd 介绍背水一战 Windows 10 之 控件(文本类) AutoSug ...

  8. 背水一战 Windows 10 (29) - 控件(文本类): RichTextBlock, RichTextBlockOverflow, RichEditBox

    [源码下载] 背水一战 Windows 10 (29) - 控件(文本类): RichTextBlock, RichTextBlockOverflow, RichEditBox 作者:webabcd ...

  9. 背水一战 Windows 10 (28) - 控件(文本类): TextBox, PasswordBox

    [源码下载] 背水一战 Windows 10 (28) - 控件(文本类): TextBox, PasswordBox 作者:webabcd 介绍背水一战 Windows 10 之 控件(文本类) T ...

随机推荐

  1. 【Java EE 学习 49 下】【Spring学习第一天】【MVC】【注解回顾】

    一.MVC 1.使用Spring有一个非常大的好处,那就是能够实现完全面向接口编程,传统的使用Dao.Service并不能实现完全的面向接口编程. 2.示例:https://github.com/kd ...

  2. python信号signal简单示例

    进程间通信之类的,用得着, 可以自定义接到信息之后的动作. file1.py #!/usr/bin/env python # -*- coding: utf-8 -*- import os impor ...

  3. linux日志文件

    linux日志文件 在系统运行正常的情况下学习了解这些不同的日志文件有助于你在遇到紧急情况时从容找出问题并加以解决. /var/log/messages — 包括整体系统信息,其中也包含系统启动期间的 ...

  4. java中定时器的四种方法

    package com.lid; import java.util.Calendar; import java.util.Date; import java.util.Timer; import ja ...

  5. poj2631 求树的直径裸题

    题目链接:http://poj.org/problem?id=2631 题意:给出一棵树的两边结点以及权重,就这条路上的最长路. 思路:求实求树的直径. 这里给出树的直径的证明: 主要是利用了反证法: ...

  6. 从零开始山寨Caffe·零:必先利其器

    工作环境 巧妇有了米炊 众所周知,Caffe是在Linux下写的,所以长久以来,大家都认为跑Caffe,先装Linux. niuzhiheng大神发起了caffe-windows项目(解决了一些编译. ...

  7. codeforces#271 (Div. 2)预处理

    B. Worms time limit per test 1 second memory limit per test 256 megabytes input standard input outpu ...

  8. 解决使用osgModeling的Loft生成管子时的bug

    最近在使用osgModeling的Loft生成管子的时候, 发现这个类还是有点bug的. 具体的表现就是在某些情况下, 生成管子的某些节点会是扁的, 而且有时管子会莫名的变粗.   在网上各种求助无果 ...

  9. C#中的简单工厂和单例

    下面首先来说说简单工厂 举个例子: 首先是父类 public abstract class Pizza { public abstract string Info(); } } 子类 public c ...

  10. [转]SQL Server 存储过程 一些常用用法(事物、异常捕捉、循环)

      最新更新请访问: http://denghejun.github.io Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中 ...