iOS触摸事件处理

主要是记录下iOS的界面触摸事件处理机制,而后用一个实例来讲明下应用场景.数组

1、处理机制ide

界面响应消息机制分两块,(1)首先在视图的层次结构里找到能响应消息的那个视图。(2)而后在找到的视图里处理消息。spa

【关键】(1)的过程是从父View到子View查找,而(2)是从找到的那个子View往父View回溯(不必定会往回传递消息)。对象

 

1.一、寻找响应消息视图的过程能够借用M了个J的一张图来讲明。blog

处理原理以下:继承

• 当用户点击屏幕时,会产生一个触摸事件,系统会将该事件加入到一个由UIApplication管理的事件队列中递归

 UIApplication会从事件队列中取出最前面的事件进行分发以便处理,一般,先发送事件给应用程序的主窗口(UIWindow)队列

 主窗口会调用hitTest:withEvent:方法在视图(UIView)层次结构中找到一个最合适的UIView来处理触摸事件
事件

(hitTest:withEvent:实际上是UIView的一个方法,UIWindow继承自UIView,所以主窗口UIWindow也是属于视图的一种)
it

• hitTest:withEvent:方法大体处理流程是这样的:

首先调用当前视图pointInside:withEvent:方法判断触摸点是否在当前视图内:

▶ 若pointInside:withEvent:方法返回NO,说明触摸点不在当前视图内,则当前视图hitTest:withEvent:返回nil

▶ 若pointInside:withEvent:方法返回YES,说明触摸点当前视图内,则遍历当前视图的全部子视图(subviews),调用子视图hitTest:withEvent:方法重复前面的步骤,子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图hitTest:withEvent:方法返回非空对象或者所有子视图遍历完毕:

▷ 若第一次有子视图hitTest:withEvent:方法返回非空对象,则当前视图hitTest:withEvent:方法就返回此对象,处理结束

▷ 若全部子视图hitTest:withEvent:方法都返回nil,则当前视图hitTest:withEvent:方法返回当前视图自身(self)

• 最终,这个触摸事件交给主窗口的hitTest:withEvent:方法返回的视图对象去处理。

拿到这个UIView后,就调用该UIView的touches系列方法。

1.二、消息处理过程,在找到的那个视图里处理,处理完后根据须要,利用响应链nextResponder可将消息往下一个响应者传递。

UIAppliactionDelegate <- UIWindow <- UIViewController <- UIView <- UIView

【关键】:要理解的有三点:一、iOS判断哪一个界面能接受消息是从View层级结构的父View向子View传递,即树状结构的根节点向叶子节点递归传递。二、hitTest和pointInside成对,且hitTest会调用pointInside。三、iOS的消息处理是,当消息被人处理后默认再也不向父层传递。

 

2、应用实例

【需求】是:界面以下,

Window

  -ViewA

    -ButtonA

    -ViewB

      -ButtonB

层次结构:ViewB彻底盖住了ButtonA,ButtonB在ViewB上,如今须要实现1)ButtonA和ButtonB都能响应消息 2)ViewA也能收到ViewB所收到的touches消息 3)不让ViewB(ButtonB)收到消息。

(首先解析下,默认状况下,点击了ButtonB的区域,iOS消息处理过程。

-ViewA 

  -ButtonA

  -ViewB

    -ButtonB

当点击ButtonB区域后,处理过程:从ViewA开始依次调用hitTest

pointInside的值依次为:

ViewA:NO;

ViewB:YES;

ButtonB:YES;

ButtonB的subViews:NO;

因此ButtonB的subViews的hitTest都返回nil,因而返回的处理对象是ButtonB本身。接下去开始处理touches系列方法,这里是调用ButtonB绑定的方法。处理完后消息就中止,整个过程结束。)

【分析】:

实现的方式多种,这里将两个需求拆解开来实现,由于实现2就能够知足1。

2.一、需求1的实现,ViewB盖住了ButtonA,因此默认状况下ButtonA收不到消息,可是在消息机制里寻找消息响应是从父View开始,因此咱们能够在ViewA的hitTest方法里作判断,若touch point是在ButtonA上,则将ButtonA做为消息处理对象返回。

代码以下:

#pragma mark - hitTest
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 当touch point是在_btn上,则hitTest返回_btn
    CGPoint btnPointInA = [_btn convertPoint:point fromView:self];
    if ([_btn pointInside:btnPointInA withEvent:event]) {
        return _btn;
    }
    
    // 不然,返回默认处理
    return [super hitTest:point withEvent:event];
    
}

这样,当触碰点是在ButtonA上时,则touch消息就被拦截在ViewA上,ViewB就收不到了。而后ButtonA就收到touch消息,会触发onClick方法。

2.二、需求2的实现,上面说到响应链,ViewB只要override掉touches系列的方法,而后在本身处理完后,将消息传递给下一个响应者(即父View即ViewA)。

代码以下:在ViewB代码里

#pragma mark - touches
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"B - touchesBeagan..");
    
    // 把事件传递下去给父View或包含他的ViewController
    [self.nextResponder touchesBegan:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"B - touchesCancelled..");
    // 把事件传递下去给父View或包含他的ViewController
    [self.nextResponder touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"B - touchesEnded..");
    // 把事件传递下去给父View或包含他的ViewController
    [self.nextResponder touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"B - touchesMoved..");
    // 把事件传递下去给父View或包含他的ViewController
    [self.nextResponder touchesBegan:touches withEvent:event];
    
}

而后,在ViewA上就能够接收到touches消息,在ViewA上写:

#pragma mark - touches
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"A - touchesBeagan..");
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"A - touchesCancelled..");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"A - touchesEnded..");
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"A - touchesMoved..");
    
}

这样就实现了向父View透传消息。

2.3 、不让ViewB收到消息,能够设置ViewB.UserInteractionEnable=NO;除了这样还能够override掉ViewB的ponitInside,原理参考上面。

在ViewB上写:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // 本View不响应用户事件
    return NO;
 
}
相关文章
相关标签/搜索