app触摸一下 ios
2014-09-06 12:43:05 EI__Nino 阅读数 2162


监听事件

$(document).bind('touchmove', function(e) {           
	 e.preventDefault();  
});  


2016-03-31 17:18:31 xumugui007 阅读数 181

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

一、处理机制

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

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

 

1.1、寻找响应消息视图的过程可以借用M了个J的一张图来说明。

处理原理如下:

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

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

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

(hitTest:withEvent:其实是UIView的一个方法,UIWindow继承自UIView,因此主窗口UIWindow也是属于视图的一种)

• 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.2、消息处理过程,在找到的那个视图里处理,处理完后根据需要,利用响应链nextResponder可将消息往下一个响应者传递。

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

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

 

二、应用实例

【需求】是:界面如下,

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:yes;

ViewB:YES;

ButtonB:YES;

ButtonB的subViews:NO;

所以ButtonB的subViews的hitTest都返回nil,于是返回的处理对象是ButtonB自己。接下去开始处理touches系列方法,这里是调用ButtonB绑定的方法。处理完后消息就停止,整个过程结束。)

【分析】:

实现的方式多种,这里将两个需求拆解开来实现,因为实现2就可以满足1。

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

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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、需求2的实现,上面说到响应链,ViewB只要override掉touches系列的方法,然后在自己处理完后,将消息传递给下一个响应者(即父View即ViewA)。

代码如下:在ViewB代码里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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上写:

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

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

在ViewB上写:

 

 

 

分类: ios

2018-10-18 17:00:00 weixin_34050519 阅读数 39

前言

在 iOS 中,常见的事件有:触摸事件、加速计事件、远程控制事件等。在这里我们主要讨论触摸事件,对于触摸事件的传递流程,我们需要先了解响应者对象和响应者链是什么,这样子才可以更加清晰的认识事件的传递流程和响应流程,然后再利用这些知识点来解决业务需求。

响应者对象

只有响应者对象才可以接收处理事件,在 iOS 中,只有 UIResponder 及其子类称为响应者对象,平时我们的 UIApplicationUIViewControllerUIView 都是继承自 UIResponder,所以它们都是响应者对象,可以接收处理事件。对于 CALayer 不是继承自 UIResponder 的,这就是为什么 CALayer 没有响应事件的能力。

对于触摸事件,UIResponder 提供了下面方法来处理触摸事件:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

触摸事件的产生和传递

用户触摸屏幕产生事件,系统将事件交给 UIApplication 管理分发,UIApplication 将事件分发给 KeyWindow,然后再寻找出一个最合适的响应者来响应这个事件。

如何寻找出最合适的响应者,主要依靠下面两个函数:

//返回最合适的 View 来响应事件
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;  
// 判断当前的触摸点是否在 View 中
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; 

这里引用 初探 iOS 事件分发机制 解释:

Hit-Test View:当用户与触摸屏产生交互时,硬件就会探测到物理接触并且通知操作系统。操作系统就会创建相应的事件,并将其传递给当前正在运行的应用程序的事件队列。然后这个事件会被事件循环传递给优先响应对象,既 Hit-Test View

Hit-Testing:Hit-Test View 就是事件被触发时和用户交互的对象,寻找 Hit-Test View 的过程就叫做 Hit-Testing

现在我们知道事件的传递是靠上面两个方法来寻找最合适的响应者,找到响应者后会调用响应者的 touch 函数进行事件处理,大概流程是:

产生触摸事件 -> UIApplication 事件队列 -> [UIWindow hitTest:withEvent:] -> 返回更合适的view -> [子控件 hitTest:withEvent:] -> 返回最合适的view -> [Application sendEvent] -> 调用最合适 view 的 touch 函数处理事件

响应者链及事件响应流程

页面的控件具有层级关系,响应者也会有层级关系,由响应者组成层级关系称为响应者链。UIResponder 中有个 nextResponder 属性返回下一个响应者对象。当一个响应者接收到事件但是不能处理时候,会交给下一个响应者去处理,最终要是谁都处理不了该事件,则会抛弃这个事件。

对于响应者链,可以参考下图:

1055199-2a49a16e1e483b5c.png

事件传递和事件响应区别

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

实战-子视图和父视图同时处理事件

子视图重写 touch 函数来处理事件,然后再调用 super touch 将事件传递给父视图:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);
    //子视图处理该事件
    
    //调用 super 让父视图也处理该事件
    [super touchesBegan:touches withEvent:event];
}

实战-扩大一个视图的点击范围

可以通过 pointInside 函数,将该视图周围的触摸事件也当成自己的事件处理:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect relativeFrame = self.bounds;
    UIEdgeInsets hitTestEdgeInsets = UIEdgeInsetsMake(-15, -15, -15, -15);
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

实战-深层级 View 通讯

假设控制器上面添加 AView,AView 添加了 BView,BView 又添加了 CView 等,在 CView 产生了一个事件需要让控制器来处理,这个时候如果用 Block、Delegate、Notification 都会比较麻烦,这个时候可以通过响应者链,将消息传递上去。

  1. 首先我们为 UIResponder 写个分类方法,类似 Router 方法
  2. 只需要在 CView 中调用该方法,让控制器去监听该方法就 OK 了

具体代码实现:

//UIResponder 分类实现
/**
 发送一个路由器消息, 对eventName感兴趣的 UIResponsder 可以对消息进行处理
 
 @param eventName 发生的事件名称
 @param userInfo 传递消息时, 携带的数据, 数据传递过程中, 会有新的数据添加
 */
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSObject *)userInfo {
    [[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}

//CView 调用
[self routerEventWithName:@"CViewEvent" userInfo:nil];

//控制器监听
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSObject *)userInfo {
    NSLog(@"%s eventName:%@",__func__,eventName);
}

实战-HitTest 大概实现

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha <= 0.01 || self.userInteractionEnabled == NO || self.hidden) {
        return nil;
    }
    
    BOOL inside = [self pointInside:point withEvent:event];
    if (inside) {
        NSArray *subViews = self.subviews;
        // 对子视图从上向下找
        for (NSInteger i = subViews.count - 1; i >= 0; i--) {
            UIView *subView = subViews[i];
            CGPoint insidePoint = [self convertPoint:point toView:subView];
            UIView *hitView = [subView hitTest:insidePoint withEvent:event];
            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
    return nil;
}

总结

这篇我们主要了解了响应者对象是什么,事件的传递流程以及事件响应流程。了解了这些知识后,还是对我们平时开发有所帮助的。

对于更加详细的介绍,可以看看后面的博客链接。

参考文献

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

Hit-Testing in iOS

深入浅出iOS事件机制

iOS事件处理,看我就够了~

初探 iOS 事件分发机制

2015-04-03 21:20:04 JianglongHuang 阅读数 1456

一:触摸

//触摸开始

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{

   //UITouch的常用属性

    UITouch *touch = [touches anyObject];

    

    //点击的次数

    NSLog(@"tap count: %ld", touch.tapCount);

    

    //触摸的阶段

    NSLog(@"phase: %ld", touch.phase);

    

    //在视图上的位置坐标

    CGPoint point = [touch locationInView:self];

    NSLog(@"location in view: %@", NSStringFromCGPoint(point));

    

    CGPoint windowPoint = [touch locationInView:[UIApplication sharedApplication].keyWindow];

    

    NSLog(@"location in window: %@", NSStringFromCGPoint(windowPoint));

    

    //在视图上前一个点的坐标

    CGPoint prePoint = [touch previousLocationInView:self];

    NSLog(@"previous location in view: %@", NSStringFromCGPoint(prePoint));


}


//触摸移动

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

{

//    NSLog(@"触摸移动");

    //UITouch的常用属性

    UITouch *touch = [touches anyObject];

    

    //在视图上的位置坐标

    CGPoint point = [touch locationInView:self];

    NSLog(@"location in view: %@", NSStringFromCGPoint(point));

    

    //在视图上前一个点的坐标

    CGPoint prePoint = [touch previousLocationInView:self];

    NSLog(@"previous location in view: %@", NSStringFromCGPoint(prePoint));

}



//触摸结束

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{


  NSLog(@"触摸结束");


}


//触摸被取消

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

{

     NSLog(@"触摸被取消");

}

 

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

    UITouch *touch = [touches anyObject];

    

    if (touch.tapCount == 1) {

        //单击

        //[self singleTap];

        [self performSelector:@selector(singleTap)

                   withObject:nil

                   afterDelay:0.2];

    } else if (touch.tapCount == 2) {

        //双击

        [NSObject cancelPreviousPerformRequestsWithTarget:self

                                                 selector:@selector(singleTap)

                                                   object:nil];

        [self doubleTap];

    }

    

}


- (void)singleTap

{

    NSLog(@"单击");

}


- (void)doubleTap

{

    NSLog(@"双击");

}


//运动事件

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event

{

    NSLog(@"摇一摇开始");

}


- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event

{

    NSLog(@"摇一摇结束");

}


- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event

{

    

}


//远程控制事件

- (void)remoteControlReceivedWithEvent:(UIEvent *)event

{

    

}


二:手势

   //1.轻击

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];

    

    tap.numberOfTapsRequired = 2;

    tap.numberOfTouchesRequired = 3;

    

    [self.view addGestureRecognizer:tap];

    

    //2.捏合

    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAction:)];

    [self.view addGestureRecognizer:pinch];

    

    //3.拖移

    //UISwipeGestureRecognizer

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction)];

    [self.view addGestureRecognizer:pan];

//

    //4.轻扫

    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeAction)];

    [self.view addGestureRecognizer:swipe];

    

    

    //5.长按

    //UILongPressGestureRecognizer

    UILongPressGestureRecognizer *press = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(pressAction:)];

    [self.view addGestureRecognizer:press];

    

    //6.旋转

    //UIRotationGestureRecognizer

}


- (void)tapAction:(UITapGestureRecognizer *)sender

{

    NSLog(@"tap");

}


- (void)pinchAction:(UIPinchGestureRecognizer *)sender

{

    NSLog(@"");

}


- (void)panAction

{

    NSLog(@"pan");

}


- (void)swipeAction

{

    NSLog(@"swipe");

}


- (void)pressAction:(UILongPressGestureRecognizer *)sender

{

    NSLog(@"press");

    

    if(sender.state == UIGestureRecognizerStateBegan)

    {

        NSLog(@"开始");

    } else if (sender.state == UIGestureRecognizerStateChanged)

    {

        NSLog(@"移动");

    } else if (sender.state == UIGestureRecognizerStateEnded)

    {

        NSLog(@"结束");

    }

    

}







2015-08-30 10:48:48 qw963895582 阅读数 341

多触摸

1.手势:指当一个手指或者多个手指开始点触屏幕开始,直到手势离开手机屏幕之间发生的所有事件。
苹果规定,无论这个过程耗时多长,只要还有手指停留在屏幕上,就处于某个手势之中,除非发生意外情况。

2.触摸:指手指放在屏幕上,手势中设计的触摸数量就是屏幕上的手指数量。

3.手势如何处理的:手势沿着响应者链传递,并且嵌入到事件中。一般需要把代码嵌入在UIView的子类中,也可以放在UIViewController中。对于放在UIView中还是UIViewController中,有个原则:如果视图需要根据用户的触摸来对自己执行某些动作,那么代码就应该写在UIView中;如果对多个对象产生影响,就应该写在UIViewController中。


响应者链

1.当我们在屏幕上完成某个手势后,系统是怎么获取的?首先要直到手势在事件之内传递到系统的,然后事件会传递到相应者链。

一。相应者对象

1.响应者就是响应事件并对其进行处理的对象,而第一响应者就是负责接收事件的响应者对象,第一响应者就是响应者链的开始。

2.UIResponder是所有响应者对象的基类,它不仅处理事件,而且也为常见的响应者行为定义编程接口。UIApplication、UIView和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或者间接地继承自UIResponder类。UIView继承自UIResponder,UIControl继承自UIView。

3.第一响应者往往首当其冲和用户进行交互,它通常是一个UIView对象。如果第一响应者没有进行处理,系统将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。

二。响应者链

1.用户对屏幕的所有操作都可成为事件,当用户手指触击屏幕及在屏幕上移动时,系统会不断把事件对象发送给应用程序。

2.响应者链处理事件的几个原则

  • 第一位处理:第一响应者收到用户的动作后,事件或动作消息会传递给它的UIView或者UIViewController
  • 传递处理:如果一个UIView或者UIViewController不能处理这个事件或动作消息,它将传递给视图的父视图,如果依然不能处理,那么会沿着视图层次结构继续前进,传递给后续的父视图。
  • UIWindow处理:如果最顶层的视图也不能处理这个事件或动作消息,那么就传递给UIWindow对象来处理。
  • UIApplication处理:如果UIWindow对象也不能处理,就传递给应用程序对象UIApplication。到最后发现应用程序对象也不能处理这个事件或动作消息,系统会抛弃不处理

  • 4个触摸通知方法

    1.要对触摸事件作出处理,一般需要重写UIResponder类中定义的事件处理函数,一般共有4个函数,系统根据不同的触摸状态通知相应者当前的触摸情况,然后再从程序里会调用对应的函数。

    2.UIResponder中4个触摸通知方法:

    触摸开始:- (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;
        其中,参数touches:表示触摸产生的所有UITouch对象
             参数event:表示特定的事件
    

    3.UITouch:触摸类

  • 获取手指数量:[touches count]

    UITouch就是触摸对象,一个UITouch表示一个手指正在触摸屏幕,touches中元素的个数就是当前按压屏幕的手指数量。
  • 获取轻击数量:@property(nonatomic,readonly) NSUInteger tapCount;

    如果该触摸是一系列轻击的一部分,则可以通过任意的UITouch对象查询轻击数量。
  • 获取手指位置:- (CGPoint)locationInView:(UIView *)view;

    每个手指在屏幕上都有各自的位置,可以通过UITouch对象来查询特定手指的位置:[touch locationInView:self.view]
  • 获取上一次触摸位置:- (CGPoint)previousLocationInView:(UIView *)view;


    4.UIEvent:事件类
  • 获取所有的触摸对象:- (NSSet *)allTouches;
  • 获取任意一个触摸对象:
    [[event allTouches] anyObject]

  • 手势识别

    一。UIGestureRecognizer类

    1.UIGestureRecognizer是一个抽象类,定义了所有手势的基本行为,它包含一系列子类,每个子类都用于识别指定的手势,其子类主要有以下几种:

  • UITapGestureRecognizer:轻击手势,可以判断单击、双击动作。
  • UIPinchGestureRecognizer:捏合手势,缩放视图或改变视图某些视图控件大小时会用到该手势


    2.手势识别器状态:是一个枚举值。UIGestureRecognizerState
  • 触摸开始:UIGestureRecognizerStateBegan
  • 触摸结束:UIGestureRecognizerStateEnded
  • 二。UITapGestureRecognizer:轻击手势类


    iOS触摸事件处理

    阅读数 365

    iOS触摸事件处理

    阅读数 19

    iOS 触摸事件处理

    阅读数 332

    iOS 手势及触摸

    阅读数 759

    iOS手势及触摸

    博文 来自: u012894631

    iOS 触摸事件处理

    阅读数 451

    没有更多推荐了,返回首页