iOS全解14:事件的传递和响应机制

按照时间顺序,事件的生命周期是这样的:
  事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view、寻找最合适的view的底层实现、拦截事件的处理)->找到最合适的view后事件的处理(touches方法的重写,也就是事件的响应)

在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接受并处理事件,我们称之为“响应者对象”。以下都是继承自UIResponder的,所以都能接收并处理事件。

UIApplication
UIViewController
UIView(superView、subView)

事件的传递和响应的区别:

事件的传递:是从上到下(父控件到子控件)
事件的响应:是从下到上(顺着响应者链条向上传递:子控件到父控件)


UITouch对象

当用户用手指触摸屏幕时,会创建一个与手指相关的UITouch对象。

作用:
  • 保存着跟手指相关的信息,比如触摸的位置、时间、阶段
  • 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
  • 当手指离开屏幕时,系统会销毁相应的UITouch对象
    提 示:iPhone开发中,要避免使用双击事件!


iOS中的事件的产生和传递

1、事件的产生
  • 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,为什么是队列而不是栈?因为队列的特点是FIFO,即先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。
  • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)。
  • 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。
    找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理。
2、事件的传递

触摸事件的传递是从父控件传递到子控件
也就是UIApplication->window->寻找处理事件最合适的view
注 意:如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件

应用如何找到最合适的控件来处理事件?

1.首先判断主窗口(keyWindow)自己是否能接受触摸事件
2.判断触摸点是否在自己身上
3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
4.view,比如叫做fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的子控件,直至没有更合适的view为止。
5.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。
UIView不能接收触摸事件的三种情况:

  • 不允许交互:userInteractionEnabled = NO
  • 隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件
  • 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明。
    注 意:默认UIImageView不能接受触摸事件,因为不允许交互,即userInteractionEnabled = NO。所以如果希望UIImageView可以交互,需要设置UIImageView的userInteractionEnabled = YES。

总结一下

1.点击一个UIView或产生一个触摸事件A,这个触摸事件A会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。
2.UIApplication会从事件对列中取出最前面的事件(此处假设为触摸事件A),把事件A传递给应用程序的主窗口(keyWindow)。
3.窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)



//==================" 系统框架 "==================

#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <UIKit/UIKitDefines.h>

NS_ASSUME_NONNULL_BEGIN

@class UIWindow, UIView, UIGestureRecognizer;

typedef NS_ENUM(NSInteger, UITouchPhase) {
    UITouchPhaseBegan,             // 当手指接触表面时。
    UITouchPhaseMoved,             // 当手指在表面移动。
    UITouchPhaseStationary,        // 当手指接触表面,但自上次事件后没有移动。       
    UITouchPhaseEnded,             // 当手指离开表面时。
    UITouchPhaseCancelled,         // 当触摸没有结束,但我们需要停止跟踪时(例如,将设备面对面)
    UITouchPhaseRegionEntered   API_AVAILABLE(ios(13.4) // 当触摸进入用户界面区域时
    UITouchPhaseRegionMoved     API_AVAILABLE(ios(13.4) // 当触摸在用户界面的区域内,但还没有接触或离开该区域时
    UITouchPhaseRegionExited    API_AVAILABLE(ios(13.4) // 当触摸退出用户界面区域时
};

typedef NS_ENUM(NSInteger, UIForceTouchCapability) {
    UIForceTouchCapabilityUnknown = 0,
    UIForceTouchCapabilityUnavailable = 1,
    UIForceTouchCapabilityAvailable = 2
};

typedef NS_ENUM(NSInteger, UITouchType) {
    UITouchTypeDirect,                       // 用手指(在屏幕上)直接触摸
    UITouchTypeIndirect,                     // 间接触摸(不是屏幕)
    UITouchTypePencil,                      // 添加铅笔名变体
    UITouchTypeStylus = UITouchTypePencil,  // 触控笔的触摸(不建议使用铅笔)
    UITouchTypeIndirectPointer API_AVAILABLE(ios(13.4) //一种表示基于按钮的间接输入设备的触摸,描述从按钮按下到按钮释放的输入顺序
} API_AVAILABLE(ios(9.0));

typedef NS_OPTIONS(NSInteger, UITouchProperties) {
    UITouchPropertyForce = (1UL << 0),
    UITouchPropertyAzimuth = (1UL << 1),
    UITouchPropertyAltitude = (1UL << 2),
    UITouchPropertyLocation = (1UL << 3), // 为预测触动
} API_AVAILABLE(ios(9.1));

UIKIT_EXTERN API_AVAILABLE(ios(2.0)) NS_SWIFT_UI_ACTOR
@interface UITouch : NSObject

@property(nonatomic,readonly) NSTimeInterval    timestamp;      // 记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) UITouchPhase  phase;          // 当前触摸事件所处的状态
@property(nonatomic,readonly) NSUInteger        tapCount;       // 短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) UITouchType       type 
@property(nonatomic,readonly) CGFloat majorRadius;          // majorRadius和majorRadiusTolerance以点数表示
@property(nonatomic,readonly) CGFloat majorRadiusTolerance; // majorRadius将是精确的+/- majorRadiusTolerance
@property(nullable,nonatomic,readonly,strong) UIWindow                        *window;          // 触摸产生时所处的窗口
@property(nullable,nonatomic,readonly,strong) UIView                          *view;                // 触摸产生时所处的视图
@property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers; // 触摸手势

- (CGPoint)locationInView:(UIView *)view;                   // 触摸在view上的位置
- (CGPoint)previousLocationInView:(UIView *)view;       // 记录了前一个触摸点的位置
- (CGPoint)preciseLocationInView:(UIView *)view;            // 使用这些方法获得额外的精度,可能从触摸可用。
- (CGPoint)precisePreviousLocationInView:(UIView *)view; // 不要使用精确的位置进行命中测试。触摸可能会在视图内部进行测试,但在视图外部有一个精确的位置。

@property(nonatomic,readonly) CGFloat force;                    //触摸的力,其中1.0代表平均触摸的力
@property(nonatomic,readonly) CGFloat maximumPossibleForce; //使用这个输入机制最大可能的力量

//1 方位角。仅对触控笔触摸类型有效。零弧度点沿X轴正方向。为视图参数传递一个空值将返回相对于触摸窗口的方位角。
//2 指向方位角方向的单位向量。仅对触控笔触摸类型有效。为视图参数传递nil将返回一个相对于触摸窗口的单位向量。
- (CGFloat)azimuthAngleInView:(nullable UIView *)view;
- (CGVector)azimuthUnitVectorInView:(nullable UIView *)view;

// 高度角:仅对触控笔触摸类型有效。0弧度表示触控笔与屏幕表面平行,当M_PI/2弧度表示它是垂直于屏幕表面。
@property(nonatomic,readonly) CGFloat altitudeAngle;

//1 一个索引,允许你关联更新与原始触摸。只有当这个UITouch预期或是一个更新时才保证非nil。
//2 一个属性的集合,它具有估计值,仅表示当前估计的属性
//3 期望在将来有更新的属性集。如果未对估价值进行更新,则当前值为最终估价值。当从边缘进入时,方位/高度值会发生这种情况
@property(nonatomic,readonly) NSNumber * _Nullable estimationUpdateIndex;
@property(nonatomic,readonly) UITouchProperties estimatedProperties;
@property(nonatomic,readonly) UITouchProperties estimatedPropertiesExpectingUpdates;


@end

NS_ASSUME_NONNULL_END

#else
#import <UIKitCore/UITouch.h>
#endif

//==================" 类别 "==================

@interface UIView(UIViewGeometry)

//可以做成动画。如果视图被转换,不要使用框架,因为它不能正确地反映视图的实际位置。使用bounds + center代替。
@property(nonatomic) CGRect            frame;

//如果非恒等变换,则使用bounds/center而不是frame。如果边界维数为奇数,中心可能有小数部分
@property(nonatomic) CGRect            bounds;      // 默认边界是0原点,帧大小。可以做成动画
@property(nonatomic) CGPoint           center;      // 中心是框架的中心。可以做成动画
@property(nonatomic) CGAffineTransform transform;   // 默认是CGAffineTransformIdentity。可以做成动画。请使用这个属性而不是图层上的affineTransform属性
@property(nonatomic) CATransform3D     transform3D API_AVAILABLE(ios(13.0),tvos(13.0)); //默认为CATransform3DIdentity。可以做成动画。请使用这个属性而不是图层上的transform属性
@property(nonatomic) CGFloat           contentScaleFactor API_AVAILABLE(ios(4.0));

@property(nonatomic,getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled API_UNAVAILABLE(tvos);   // default is NO
@property(nonatomic,getter=isExclusiveTouch) BOOL       exclusiveTouch API_UNAVAILABLE(tvos);         // default is NO

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // 递归调用-pointInside:withEvent:。点在接收器的坐标系统中
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;        // 如果点在边界内,默认返回YES(判断点在不在方法调用者的坐标系上)
- (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
- (CGPoint)convertPoint:(CGPoint)point fromView:(nullable UIView *)view;
- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
- (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view;

@property(nonatomic) BOOL               autoresizesSubviews; // 默认为YES。如果设置,子视图将根据它们的autoresizingMask if self进行调整。范围内变化
@property(nonatomic) UIViewAutoresizing autoresizingMask;    // 简单的调整。默认是UIViewAutoresizingNone

- (CGSize)sizeThatFits:(CGSize)size;    // 返回'best' size以适合给定的大小。实际上不会调整视图的大小。默认是返回现有的视图大小
- (void)sizeToFit;                  // 调用sizeethatfits:与当前视图边界和改变边界大小。

@end

引用:
史上最详细的iOS之事件的传递和响应机制-原理篇
UIView超出父view的部分视图的子视图响应事件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 前言: 按照时间顺序,事件的生命周期是这样的:事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的vie...
    朽木自雕也阅读 507评论 0 8
  • 简单分享一下iOS开发中事件在view之间如何传递,如何响应事件,如有错误,请大家指正。 一、事件的产生 应用外层...
    leesum阅读 974评论 0 1
  • 目的:了解事件传递过程和响应机制能够帮助处理一些手势冲突,自定义手势等问题 1、事件分类? 1.1 在iOS系统中...
    song91425阅读 832评论 0 1
  • 本文主要参考了 VV木公子(简书作者)的 史上最详细的iOS之事件的传递和响应机制我按照自己的理解做了排版和一些表...
    hi_xgb阅读 2,567评论 4 28
  • iOS 中的事件 触摸事件 加速计事件 远程控制事件 响应者对象(UIResponder) 只有继承 UIResp...
    jeff_guan阅读 144评论 0 0