美文网首页
事件的传递与响应机制

事件的传递与响应机制

作者: nucky_lee | 来源:发表于2019-03-18 10:37 被阅读0次

响应者对象 -- UIResponder

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

  • UIApplication
  • UIViewController
  • UIView

那么为什么继承自UIResponder的类就能够接收并处理事件呢?

因为UIResponder中提供了以下4个对象方法来处理触摸事件。

UIResponder内部提供了以下方法来处理事件触摸事件

- (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;

响应链

app中,所有的视图都是按照树状层次结构组织起来的,每个view都有自己的superView,包括controller的topmost view(controller的self.view)。当一个view被add到superView上的时候,他的nextResponder属性就会被指向它的superView,当controller被初始化的时候,self.view(topmost view)的nextResponder会被指向所在的controller,而controller的nextResponder会被指向self.view的superView,这样,整个app就通过nextResponder串成了一条链,也就是我们所说的响应链。所以响应链就是一条虚拟的链,并没有一个对象来专门存储这样的一条链,而是通过UIResponder的属性串连起来的。

一、事件的传递

1.点击UIView产生一个触摸事件,这个触摸事件会被添加到由UIApplication管理的事件队列中(即首先接收到事件的是UIApplication)。

2.UIApplication会从事件队列中取出最前面的事件(此处假设为触摸事件A),把事件A传递给应用程序的主窗口(UIWindow)。

3.UIWindow将事件向下分发给UIView。

4.UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。

5.遍历子控件,重复以上两步。

6.如果没有找到,那么自己就是事件处理者。

7.如果自己不能处理,那么不做任何处理。

其中 UIView不接受事件处理的情况主要有以下三种

1)alpha <0.01

2)userInteractionEnabled = NO

3.hidden = YES.

事件传递顺序是这样的:

产生触摸事件->UIApplication事件队列->[UIWindow hitTest:withEvent:]->返回更合适的子控件view->[子控件 hitTest:withEvent:]->返回最合适的view。

注意:即便确定最终父控件是最合适的view,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的。

重难点 -- 应用如何找到最合适的控件view来处理事件?

1.主窗口(keyWindow)接收到应用程序UIApplication传递过来的事件后, 首先判断自己是否能接收触摸事件,如果能,那么再判断触摸点在不在自己身上;

2.如果触摸点也在主窗口身上,那么窗口会从后往前(首先查找数组中最后一个元素)遍历自己的子控件

3.遍历到每一个子控件后,又会重复上面的两个步骤(传递事件给子控件,1.判断子控件能否接收事件,2.触摸点在不在子控件上)

4.循环遍历子控件,直到找到最合适的view,如果没有更合适的子控件,那么自己就成为最合适的view。

找到最合适的view后,就会调用该view的touches方法处理具体的事件。所以,只有找到最合适的view,把事件传递给最合适的view后,才会调用touches方法进行接下来的事件处理。找不到最合适的view,就不会调用touches方法进行事件处理。

注意:之所以会采取从后往前遍历子控件的方式寻找最合适的view只是为了做一些循环优化。因为相比较之下,后添加的view在上面,降低循环次数。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

  // 1.判断下窗口能否接收事件

  if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;

  // 2.判断下点在不在窗口上
  // 不在窗口上

  if ([self pointInside:point withEvent:event] == NO) return nil;

  // 3.从后往前遍历子控件数组

  int count = (int)self.subviews.count;

  for (int i = count - 1; i >= 0; i--) {

    // 获取子控件

    UIView *childView = self.subviews[I];

    // 坐标系的转换,把窗口上的点转换为子控件上的点

    // 把自己控件上的点转换成子控件上的点

    CGPoint childP = [self convertPoint:point toView:childView];

    UIView *fitView = [childView hitTest:childP withEvent:event];

    if (fitView) {

      // 如果能找到最合适的view

      return fitView;

    }

}

// 4.没有找到更合适的view,也就是没有比自己更合适的view

return self;

}

扩展:
想让谁成为最合适的view就重写谁自己的父控件的hitTest:withEvent:方法返回指定的子控件,或者重写自己的hitTest:withEvent:方法 return self。但是,建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view!
原因在于在自己的hitTest:withEvent:方法中返回自己有时候会出现问题。因为会存在这么一种情况:当遍历子控件时,如果触摸点不在子控件A自己身上而是在子控件B身上,还要要求返回子控件A作为最合适的view,采用返回自己的方法可能会导致还没有来得及遍历A自己,就有可能已经遍历了点真正所在的view,也就是B。这就导致了返回的不是自己而是触摸点真正所在的view。所以还是建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view!

二、事件的响应

首先看initial view能否处理这个事件,如果不能则会将事件传递给其父视图(inital view的superView);如果父视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传递;一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃。

在事件的响应中,如果某个控件实现了touches...方法,则这个事件将由该控件来处理,如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….方法

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

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

应用:

扩大按钮的点击区域
#import <UIKit/UIKit.h>

@interface SYExpandHitAreaButton : UIButton

//定义扩大点击区域的数值
@property (nonatomic, assign) CGFloat expandValue;

@end
#import "SYExpandHitAreaButton.h"

@interface SYExpandHitAreaButton()

@end
@implementation SYExpandHitAreaButton

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.hidden || self.alpha <= 0.01) {
        return nil;
    }
    CGRect touchRect = CGRectInset(self.bounds, self.expandValue, self.expandValue);
    if (CGRectContainsPoint(touchRect, point)) {
        for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subView convertPoint:point fromView:self];
            UIView *hitTestView = [self hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

@end

参考:

深入浅出iOS事件机制

http://zhoon.github.io/ios/2015/04/12/ios-event.html

史上最详细的iOS之事件的传递和响应机制-原理篇

https://www.jianshu.com/p/2e074db792ba

相关文章

  • 深入浅出iOS事件机制

    深入浅出iOS事件机制事件传递:响应链事件传递响应链

  • iOS之事件的传递和响应机制

    iOS之事件的传递和响应机制

  • 初识iOS事情处理机制

    参考:史上最详细的iOS之事件的传递和响应机制-原理篇iOS触摸事件全家桶史上最详细的iOS之事件的传递和响应机制...

  • 事件的传递与响应机制

    响应者对象 -- UIResponder iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象...

  • iOS事件传递和视图响应

    iOS事件响应机制的事件传递流程 - (UIView *)hitTest:(CGPoint)point withE...

  • 01进阶之路-UI视图

    1. 事件传递机制和响应者链条 学习链接 事件传递机制iOS中的事件可以分为3大类型 1 触摸事件 2 加速计...

  • iOS 事件传递机制

    事件传递机制 响应者链的事件传递过程:如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给...

  • iOS-UI部分知识点整理

    UITableView相关 事件传递&视图响应 系统的UI事件传递机制是怎么样的 ? 使UITableView滚动...

  • IOS开发 事件响应链

    本节学习内容: 1.事件响应链的概念 2.事件响应链的传递机制 3.事件响应链的应用 响应顺序 Subview>M...

  • UI事件传递&事件响应

    响应链工作原理 点击某一控件到其响应相关事件其实是分为两步:事件的传递与事件的响应 事件分发与传递:自上而下 事件...

网友评论

      本文标题:事件的传递与响应机制

      本文链接:https://www.haomeiwen.com/subject/jitvmqtx.html