iOS 事件传递面试题

没有比这里更全的了,看我就好了

面试官😃 :你了解iOS中的事件传递机制吗?

什么是事件?

当我们在手机屏幕上进行点击,轻扫,滑动,就是一个事件。

iOS中有哪些事件?

1,触摸事件。以下都以触摸事件来讲解事件的传递。

2,加速计事件

3,远程控制事件

什么对象可以处理事件?

继承了UIResponder的对象才能接收并处理事件。这些对象我们称之为“响应者对象”。比如;UIApplication,UIViewController,UIView及其子类。

事件的传递:

当发生触摸事件后,系统将事件加入到UIApplication管理的事件队列中(FIFO),UIApplication从事件队列中取出最前面的事件传递到主窗口(KeyWindow),事件一传递到KeyWindow,KeyWindow就会调用- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法,判断自己能否有接收处理事件的能力,如果有接收处理事件的能力,继续调用- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event,判断触摸点在不在当前窗口内,如果触摸点在当前窗口内,这个触摸事件就被传递到UIViewController的view上,继续调用hitTest和pointInside方法,判断是否有接收处理事件的能力以及触摸点在不在view上,如果view有接收处理事件的能力,而且当前触摸点在view上,那么就会倒叙遍历view的子视图,子视图执行hitTest和pointInside方法继续寻找最合适的view,直至找到最上层的合适的view(有处理触摸事件的能力,触摸点又在上面)。

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

只要事件一传递到一个控件,这个控件就会执行hitTest方法,寻找返回最合适的view。不管控件能不能处理事件,也不管触摸点在不在这个控件上,事件都会传递给这个控件,随后调用hitTest方法。

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

判断触摸点在不在当前view上,如果返回yes,代表触摸点在当前view内,如果返回no,代表触摸点不在当前view内。

找到了最合适的view,也就代表事件传递的第一部分结束(寻找第一响应者的过程结束,这个最合适的view就是第一响应者),接下来进入事件传递的第二部分,也就是响应链。

响应链

在寻找最合适的view的过程中,有各个响应者组成了一条响应者链。

因为最合适的view(第一响应者)拥有接收处理事件的能力,而且触摸点也在它之上,但是不代表最合适的view就是事件的处理者。即使最合适的view拥有接收处理问题的能力,但是它不一定可以响应事件。

在找到最合适的view之后,首先判断最合适的view(第一响应者)是否可以处理事件,如果最合适的view可以响应事件,就响应事件然后结束。如果最合适的view不能响应事件,就向下传递给它的俯视图,依次向下传递,直至找到可以响应事件的响应者。如果最后没有找到可以响应事件的响应者,则UIApplication将其丢弃。

实际用例:[viewController.view addSubview: SView];

[SView.view addSubview: AView];

[AView.view addSubview: BView];

[AView.view addSubview: DView];

[AView.view addSubview: CView];

灰色:SView

橘色:AView

绿色:BView

蓝色:CView

青色:DView

  面试官😃 :点击橘色的任何区域,点击事件都由橘色view来响应,怎么实现?(事件的透传)

在自定义橘色的view内,重写hitTest方法,并返回self

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    return self;
}

面试官😃 :点击橘色的任何区域,点击事件都由蓝色view来响应,怎么实现?

在自定义橘色view内,重写hitTest方法,并在方法内返回蓝色view的实例

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *v;
    for (UIView *sub in self.subviews) {
        if ([sub isKindOfClass:[UIView class]]) {
            if ([sub isMemberOfClass:[BView class]]) {
                v = sub;
            }
        }
    }
    return v;
}

面试官😃 :点击Dview的超出父视图的区域,DView会响应点击事件吗?如果不会,为什么不会响应点击事件?怎么改造,DView超出父视图的部分响应点击事件?

点击DView超出父视图额度区域,DView不会响应点击事件,因为根据事件传递机制,当事件传递到AView,AView调用hitTest方法,判断当前触摸点不在AView范围内,pointInset返回NO,事件传递不到DView上来。事件传递到viewContoller的时候,判断viewController是最合适的view,又倒叙遍历了当前view的子视图,发现没有最合适的view。所以最终viewController是最合适的view,而且viewController又可以响应事件,所以最终响应此次点击事件的对象是viewController。

如果想让DView响应超出父视图的部分响应点击事件,需要在AView中重写hitTest方法,详细请看代码中的注释

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
   
    SView *sv = [[SView alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 300)];
    sv.backgroundColor = [UIColor grayColor];
    [self.view addSubview:sv];

    
    AView *av = [[AView alloc] initWithFrame:CGRectMake(100, 50, 200, 200)];
    av.backgroundColor = [UIColor orangeColor];
    [sv addSubview:av];
    
    BView *bv = [[BView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
    bv.backgroundColor = [UIColor greenColor];
    [av addSubview:bv];
    
    DView *dv = [[DView alloc] initWithFrame:CGRectMake(150, 150, 100, 100)];
    dv.backgroundColor = [UIColor cyanColor];
    [av addSubview:dv];
    av.dv = dv;

    
    CView *cv = [[CView alloc] initWithFrame:CGRectMake(50, 130, 50, 50)];
    cv.backgroundColor = [UIColor blueColor];
    [av addSubview:cv];

}

AView.h

#import <UIKit/UIKit.h>
#import "DView.h"

NS_ASSUME_NONNULL_BEGIN

@interface AView : UIView

@property (nonatomic, strong) DView *dv;

@end

NS_ASSUME_NONNULL_END

AView.m

//方法返回的view是最合适的view
//point point坐标系是当前坐标系,代表当前触摸点在当前视图中的位置
//event 事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //父视图调用hitTest方法,判断当前触摸点在哪个视图中
    //返回哪个视图就说明当前触摸点在哪个视图中,在不重写父视图hitTest的情况下由系统判断,
    //返回nil,说明触摸点,不在当前视图以及其子视图中,
    UIView *view = [super hitTest:point withEvent:event];
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01 || !self.dv) {
        return nil;
    }
    if (view == nil) {
        //将父视图坐标系转换为自己(子视图)的坐标系
        //point坐标系是当前视图坐标系,代表当前触摸点在当前视图中的位置,
        //converPoint坐标系是子视图坐标系,代表触摸点在子视图中的位置,以当前子视图左上角为原点
        UIView *sub = self.dv;
        CGPoint converPoint = [sub convertPoint:point fromView:self];
        //判断转换后的点在不在当前子视图上,
        if ([sub pointInside:converPoint withEvent:event]) {
            return sub;
        } else {
            return view;
        }
//        这种方法也可以,实际上也是调用了pointInset
//        UIView *targetView = [sub hitTest:converPoint withEvent:event];
//        if (targetView) {
//            return targetView;
//        } else {
//            return targetView;
//        }
    }
    //如果在父视图找到了最合适的view,就返回这个最合适的view
    return view;
}

下面看网上的一种错误的代码

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *view = [super hitTest:point withEvent:event];
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
        return nil;
    }
    if (view == nil) {
        //当DView不是最后一个添加到AView,这段代码并不能实现效果
        for (UIView *sub in self.subviews.reverseObjectEnumerator) {
            CGPoint converPoint = [sub convertPoint:point fromView:self];
            UIView *v = [sub hitTest:converPoint withEvent:event];
            if (v) {
                return v;
            } else {
                return view;
            }
        }
    }
    return view;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值