【iOS开发】——事件传递链和事件响应链

【iOS开发】——事件传递链和事件响应链

事件链

事件链有事件传递链和事件响应链
用户点击屏幕时,首先 UIApplication 对象先收到该点击事件,再依次传递给它上面的所有子 view,直到传递到最上层。即由系统向最上层 view 传递,Application -> window -> root view -> sub view -> … -> first view传递链
那响应链呢,和传递链相反,由最基础的 view系统传递first view -> super view -> … -> view controller -> window -> Application -> AppDelegate 即响应链。

总结一下,事件链包含传递链和响应链,事件通过传递链传递上去,通过响应链找到相应的 UIResponse。
在这里插入图片描述

事件传递链

只有继承了 UIResponser 的对象才能够接受处理事件。UIResponse 是响应对象的基类,定义了处理各种事件的接口。在 UIKit 中我们使用响应者对象 Responder 接收和处理事件。一个响应者对象一般是 UIResponder 类的实例,它常见的子类包括 UIViewUIViewControllerUIApplication,这意味着几乎所有我们日常使用的控件都是响应者,如 UIButtonUILabel 等等。

UIResponder 及其子类中我们通过UITouch来处理和传递事件UIEvent的,也就是最开始说的点击屏幕,具体有以下几种:

open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

UITouch 内,存储了大量触摸相关的数据,当手指在屏幕上移动时,所对应的 UITouch 数据也会更新,这也是我们手机操作起来很丝滑的一个原因,另外需要注意的是,在这四个方法的参数中,传递的是 UITouch 类型的一个集合 (而不是一个 UITouch),这对应了两根及以上手指触摸同一个视图的情况。

当用户点击屏幕时,事件传递的顺序:

1. 用户在点击屏幕
2. 系统将点击事件加入到 UIApplication 管理的消息队列中;
3. UIApplication 会从消息队列中取出该事件传递给 UIWindow 对象;
4. 在 UIWindow 中调用方法 hitTest:withEvent: ,在 hitTest:withEvent: 方法中调用 pointInside:withEvent: 来判断当前点击的点是否在 UIWindow 内部;
5. 如若返回 yes,则倒序遍历其子视图找到最终响应的子 view
6. 如果最终返回一个view,那么即为最终响应 view 并结束事件传递,如果无值返回则将 UIWindow 作为响应者

在这里插入图片描述

在这里插入图片描述
两个核心方法:

// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   
// default returns YES if point is in bounds
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   
  • 方法 hitTest:withEvent: 用来获取最终响应事件的 view
  • 方法 pointInside:withEvent:,用来判断点击的位置是否在视图范围内。

事件响应链

事件响应就是将离用户最近的view传递回去,举个例子:
在这里插入图片描述

图中浅灰色的箭头是指将 UIView 直接添加到 UIWindow 上情况。
响应链应该是:ViewB ->ViewC-> ViewA -> UIViewController 对象 -> UIWindow 对象 -> UIApplication 对象 -> App Delegate

触摸事件首先将会由第一响应者响应,触发其 (target action) 等方法,根据触摸的方式不同(如拖动,双指),具体的方法和过程也不一样。若第一响应者在这个方法中不处理这个事件,则会传递给响应链中的下一个响应者触发该方法处理,若下一个也不处理,则以此类推传递下去。若到最后还没有人响应,则会被丢弃(比如一个误触)。

点击穿透事件

在这里插入图片描述
看看这两个Button重合在了一起,我们都知道点击1区域是粉色Button响应了,点击3区域是蓝色区域响应了,那我们点击区域2呢,谁来响应?接下来我们就以点击区域2粉色Button响应来学习一下点击穿透事件。

那么思路是怎么样的呢:

1. 点击蓝色按钮的区域2,粉色按钮响应事件,那肯定要重写蓝色按钮的hitTest方法
2. 在hitTest方法中,将触摸点的坐标系从蓝色按钮转换到粉色按钮上,即以粉色按钮左上角为原点
3. 坐标系转换后,判断触摸点是否在粉色按钮上,如果是,直接返回粉色按钮(严谨一点的做法是调用粉色按钮的hitTest方法),如果不是,那就调用系统的方法,让系统去处理

我们来写一下代码,既然要重新定义Button的尺寸信息,那我们就自定义一个类继承于UIButton

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BlueButton : UIButton
@property(nonatomic, weak)UIButton *PinkButton;
@end

NS_ASSUME_NONNULL_END


#import "BlueButton.h"

@implementation BlueButton

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint PinkButtonPoint = [self convertPoint:point toView:_PinkButton];
    if ([_PinkButton pointInside:PinkButtonPoint withEvent:event]) {
        return _PinkButton;
    }
    //如果希望严谨一点,可以将上面if语句及里面代码替换成如下代码
    //UIView *view = [_redButton hitTest: PinkButtonPoint withEvent: event];
    //if (view) return view;
    return [super hitTest:point withEvent:event];
}
@end

接着在ViewController里:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
//@property(nonatomic, strong) UIButton *BlueButton;
@property(nonatomic, strong) UIButton *PinkButton;

@end
 

#import "ViewController.h"
#import "BlueButton.h"
@interface ViewController ()
@property (nonatomic, strong) BlueButton *BlueButton;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _PinkButton = [UIButton buttonWithType:UIButtonTypeSystem];
    _PinkButton.frame = CGRectMake(self.view.frame.size.width / 2 - 50, self.view.frame.size.height / 2 - 50, 100, 100);
    [_PinkButton addTarget:self action:@selector(PinkTouch) forControlEvents:UIControlEventTouchUpInside];
    _PinkButton.backgroundColor = [UIColor systemPinkColor];
    [self.view addSubview:_PinkButton];
    
    self.BlueButton = [BlueButton buttonWithType:UIButtonTypeSystem];
    self.BlueButton.frame = CGRectMake(self.view.frame.size.width / 2 , self.view.frame.size.height / 2 , 100, 100);
    [self.BlueButton addTarget:self action:@selector(BlueTouch) forControlEvents:UIControlEventTouchUpInside];
    self.BlueButton.backgroundColor = [UIColor blueColor];
    self.BlueButton.PinkButton = _PinkButton;
    [self.view addSubview:self.BlueButton];
    
}

- (void)BlueTouch {
    NSLog(@"触碰蓝色按钮");
}

-(void)PinkTouch {
    NSLog(@"触碰粉色按钮");
}


@end

因为我们重写了蓝色按钮的hitTest方法,所以此时我们依次点击区域1,2,3
在这里插入图片描述

此时我们的目标就达成了
demo地址点击穿透事件demo

扩大点击区域

和点击穿透事件一样我们来重写一个类继承于UIButton,然后重写其中的方法来达到我们想要的效果

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BiggerButton : UIButton

@end


#import "BiggerButton.h"

@implementation BiggerButton

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    //当前button大小
    CGRect buttonBounds = self.bounds;
    //扩大点击区域,想缩小就将-10设为正值
    buttonBounds = CGRectInset(buttonBounds, -100, -100);
    
    //若点击的点在新的bounds里,就返回YES
    return CGRectContainsPoint(buttonBounds, point);
}

@end

我们可以看到我们重写的方法就是-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

然后我们在ViewController.m中:

#import "ViewController.h"
#import "BiggerButton.h"
@interface ViewController ()
@property(nonatomic, strong) BiggerButton *BigButton;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.BigButton = [BiggerButton buttonWithType:UIButtonTypeSystem];
    self.BigButton.frame = CGRectMake(100, 100, 100, 100);
    self.BigButton.backgroundColor = [UIColor blueColor];
    [self.BigButton addTarget:self action:@selector(BigButtonTouch) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.BigButton];
}

-(void)BigButtonTouch {
    NSLog(@"点击了扩大按钮区域");
}


@end

在这里插入图片描述

此时我们的蓝色Button的点击区域已经被放大了10倍,所以我们点击它附近的空白区域也会触发点击事件:
在这里插入图片描述

但要注意,它只是被放大了,放大之后仍有一个范围超过范围点击不会触发点击事件

demo地址扩大点击区域demo

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值