IOS事件学习总结

15 篇文章 0 订阅

iOS事件机制(一)

http://www.cnblogs.com/zhw511006/p/3517248.html

本篇内容将围绕iOS中事件及其传递机制进行学习和分析。在iOS中,事件分为三类:

触控事件(单点、多点触控以及各种手势操作)
传感器事件(重力、加速度传感器等)
远程控制事件(远程遥控iOS设备多媒体播放等)
这三类事件共同构成了iOS设备丰富的操作方式和使用体验,本次就首先来针对第一类事件:触控事件,进行学习和分析。

Gesture Recognizers

Gesture Recognizers是一类手势识别器对象,它可以附属在你指定的View上,并且为其设定指定的手势操作,例如是点击、滑动或者是拖拽。当触控事件 发生时,设置了Gesture Recognizers的View会先通过识别器去拦截触控事件,如果该触控事件是事先为View设定的触控监听事件,那么Gesture Recognizers将会发送动作消息给目标处理对象,目标处理对象则对这次触控事件进行处理,先看看如下流程图。

在iOS中,View就是我们在屏幕上看到的各种UI控件,当一个触控事件发生时,Gesture Recognizers会先获取到指定的事件,然后发送动作消息(action message)给目标对象(target),目标对象就是ViewController,在ViewController中通过事件方法完成对该事件的处理。Gesture Recognizers能设置诸如单击、滑动、拖拽等事件,通过Action-Target这种设计模式,好处是能动态为View添加各种事件监听,而不用去实现一个View的子类去完成这些功能。

以上过程就是我们在开发中在方法中常见的设置action和设置target,例如为UIButton设置监听事件等。

常用手势识别类

在UIKit框架中,系统为我们事先定义好了一些常用的手势识别器,包括点击、双指缩放、拖拽、滑动、旋转以及长按。通过这些手势识别器我们可以构造丰富的操作方式。

在上表中可以看到,UIKit框架中已经提供了诸如UITapGestureRecognizer在内的六种手势识别器,如果你需要实现自定义的手势识别器,也可以通过继承UIGestureRecognizer类并重写其中的方法来完成,这里我们就不详细讨论了。

每一个Gesture Recognizer关联一个View,但是一个View可以关联多个Gesture Recognizer,因为一个View可能还能响应多种触控操作方式。当一个触控事件发生时,Gesture Recognizer接收一个动作消息要先于View本身,结果就是Gesture Recognizer作为View处理触控事件的代表,或者叫代理。当Gesture Recognizer接收到指定的事件时,它就会发送一条动作消息(action message)给ViewController并处理。

连续和不连续动作

触控动作同时分为连续动作(continuous)和不连续动作(discrete),连续动作例如滑动和拖拽,它会持续一小段时间,而不连续动作例如单击,它瞬间就会完成,在这两类事件的处理上又稍有不同。对于不连续动作,Gesture Recognizer只会给ViewContoller发送一个单一的动作消息(action message),而对于连续动作,Gesture Recognizer会发送多条动作消息给ViewController,直到所有的事件都结束。


为一个View添加GestureRecognizer有两种方式,一种是通过InterfaceBuilder实现,另一种就是通过代码实现,我们看看通过代码来如何实现。


MyViewContoller.m


- (void)viewDidLoad {
     [super viewDidLoad];
     // 创建并初始化手势对象
     UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
          initWithTarget:self action:@selector(respondToTapGesture:)];
     // 指定操作为单击一次
     tapRecognizer.numberOfTapsRequired = 1;
     // 为当前View添加GestureRecognizer
     [self.view addGestureRecognizer:tapRecognizer];
     // ...
}
通过上述代码,我们实现了为当前MyViewController的View添加一个单击事件,首先构造了UITapGestureRecognizer对象,指定了target为当前ViewController本身,action就是后面自己实现的处理方法,这里就呼应了前文提到的Action-Target模式。


在事件处理过程中,这两种方式所处的状态又各有不同,首先,所有的触控事件最开始都是处于可用状态(Possible),对应UIKit里面的UIGestureRecognizerStatePossible类,如果是不连续动作事件,则状态只会从Possible转变为已识别状态(Recognized,UIGestureRecognizerStateRecognized)或者是失败状态(Failed,UIGestureRecognizerStateFailed)。例如一次成功的单击动作,就对应了Possible-Recognized这个过程。


如果是连续动作事件,如果事件没有失败并且连续动作的第一个动作被成功识别(Recognized),则从Possible状态转移到Began(UIGestureRecognizerStateBegan)状态,这里表示连续动作的开始,接着会转变为Changed(UIGestureRecognizerStateChanged)状态,在这个状态下会不断循环的处理连续动作,直到动作执行完成变转变为Recognized已识别状态,最终该动作会处于完成状态(UIGestureRecognizerStateEnded),另外,连续动作事件的处理状态会从Changed状态转变为Canceled(UIGestureRecognizerStateCancelled)状态,原因是识别器认为当前的动作已经不匹配当初对事件的设定了。每个动作状态的变化,Gesture Recognizer都会发送消息(action message)给Target,也就是ViewController,它可以根据这些动作消息进行相应的处理。例如一次成功的滑动手势动作就包括按下、移动、抬起的过程,分别对应了Possible-Began-Changed-Recognized这个过程。


UITouch & UIEvent


在屏幕上的每一次动作事件都是一次Touch,在iOS中用UITouch对象表示每一次的触控,多个Touch组成一次Event,用UIEvent来表示一次事件对象。


在上述过程中,完成了一次双指缩放的事件动作,每一次手指状态的变化都对应事件动作处理过程中得一个阶段。通过Began-Moved-Ended这几个阶段的动作(Touch)共同构成了一次事件(Event)。在事件响应对象UIResponder中有对应的方法来分别处理这几个阶段的事件。


touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
后面的参数分别对应UITouchPhaseBegan、UITouchPhaseMoved、UITouchPhaseEnded、UITouchPhaseCancelled这几个类。用来表示不同阶段的状态。


事件传递


如上图,iOS中事件传递首先从App(UIApplication)开始,接着传递到Window(UIWindow),在接着往下传递到View之前,Window会将事件交给GestureRecognizer,如果在此期间,GestureRecognizer识别了传递过来的事件,则该事件将不会继续传递到View去,而是像我们之前说的那样交给Target(ViewController)进行处理。


响应者链(Responder Chain)


通常,一个iOS应用中,在一块屏幕上通常有很多的UI控件,也就是有很多的View,那么当一个事件发生时,如何来确定是哪个View响应了这个事件呢,接下来我们就一起来看看。


寻找hit-test view


什么是hit-test view呢?简单来说就是你触发事件所在的那个View,寻找hit-test view的过程就叫做Hit-Testing。那么,系统是如何来执行Hit-Testing呢,首先假设现在有如下这么一个UI布局,一种有ABCDE五个View。


假设一个单击事件发生在了View D里面,系统首先会从最顶层的View A开始寻找,发现事件是在View A或者其子类里面,那么接着从B和C找,发现事件是在C或者其子类里面,那么接着到C里面找,这时发现事件是在D里面,并且D已经没有子类了,那么hit-test view就是View D啦。


响应者对象(Responsder Object)


响应者对象是能够响应并且处理事件的对象,UIResponder是所有响应者对象的父类,包括UIApplication、UIView和UIViewController都是UIResponder的子类。也就意味着所有的View和ViewController都是响应者对象。


第一响应者(First Responder)


第一响应者是第一个接收事件的View对象,我们在Xcode的Interface Builder画视图时,可以看到视图结构中就有First Responder。


这里的First Responder就是UIApplication了。另外,我们可以控制一个View让其成为First Responder,通过实现 canBecomeFirstResponder方法并返回YES可以使当前View成为第一响应者,或者调用View的becomeFirstResponder方法也可以,例如当UITextField调用该方法时会弹出键盘进行输入,此时输入框控件就是第一响应者。


事件传递机制


如上所说,,如果hit-test view不能处理当前事件,那么事件将会沿着响应者链(Responder Chain)进行传递,知道遇到能处理该事件的响应者(Responsder Object)。通过下图,我们来看看两种不同情况下得事件传递机制。


左边的情况,接收事件的initial view如果不能处理该事件并且她不是顶层的View,则事件会往它的父View进行传递。initial view的父View获取事件后如果仍不能处理,则继续往上传递,循环这个过程。如果顶层的View还是不能处理这个事件的话,则会将事件传递给它们的ViewController,如果ViewController也不能处理,则传递给Window(UIWindow),此时Window不能处理的话就将事件传递给Application(UIApplication),最后如果连Application也不能处理,则废弃该事件。


右边图的流程唯一不同就在于,如果当前的ViewController是由层级关系的,那么当子ViewController不能处理事件时,它会将事件继续往上传递,直到传递到其Root ViewController,后面的流程就跟之前分析的一样了。


这就是事件响应者链的传递机制,通过这些内容,我们可以更深入的了解事件在iOS中得传递机制,对我们在实际开发中更好的理解事件操作的原理有很大的帮助,也对我们实现复杂布局进行事件处理时增添了多一份的理解。


总结


通过前面的内容分析,我们已经学习并了解了如下内容:


Gesture Recognizers,是用来控制手势识别的过程和方法,并且其通过Action-Target模式与ViewController的通信的方式。连续和不连续手势动作情况下GestureRecognizer的状态转变。
UITouch和UIEvent对象,他们都是UIKit中来进行事件处理的对象,多个UITouch对象构成一个UIEvent对象,重写相应的方法可以控制和处理事件各个阶段的操作。
系寻找hit-test view的方式、事件传递机、制响应者链
后记:本篇是iOS事件传递机制的上篇,下篇将继续讨论多点触控事件和手势操作的内容!
========

iOS事件分发机制(一) hit-Testing

http://ios.jobbole.com/81864/
  
iOS中的事件大概分为三种,分别是 Milti-Touch Events, Motion Events 和Remote Control Events(events for controlling multimedia)。


本文将主要针对TouchEvents的分发,做一个详细的介绍。先抛出一个问题,文章的后续部分会对问题进行解答:iOS7原生的自带NavigationController可以实现从最左侧拖动PopViewController(大约13pt),不管当前可见的ViewController有没有其他的滑动手势或者事件,这是为什么?如何实现。


我们已经处理过太多触摸事件了,比如按钮的点击事件,一些View的手势等等。那到底我们点一下屏幕,当前的View是如何知道他被点击了呢,这个就要通过HitTest来确定了


每当我们点击了一下iOS设备的屏幕,UIKit就会生成一个事件对象UIEvent,然后会把这个Event分发给当前active的app(官方原文说:Then it places the event object in the active app’s event queue.)


告知当前活动的app有事件之后,UIApplication 单例就会从事件队列中去取最新的事件,然后分发给能够处理该事件的对象。UIApplication 获取到Event之后,Application就纠结于到底要把这个事件传递给谁,这时候就要依靠HitTest来决定了。


iOS中,hit-Testing的作用就是找出这个触摸点下面的View是什么,HitTest会检测这个点击的点是不是发生在这个View上,如果是的话,就会去遍历这个View的subviews,直到找到最小的能够处理事件的view,如果整了一圈没找到能够处理的view,则返回自身。来一个简单的图说明一下


假设我们现在点击到了图中的E,hit-testing将进行如下步骤的检测(不包含重写hit-test并且返回非默认View的情况)


1、触摸点在ViewA内,所以检查ViewA的Subview B、C


2、触摸点不在ViewB内,触摸点在ViewC内部,所以检查ViewC的Subview D、E


3、触摸点不在ViewD内,触摸点发生在ViewE内部,并且ViewE没有subview,所以ViewE属于ViewA中包含这个点的最小单位,所以ViewE变成了该次触摸事件的hit-TestView


PS.


1、默认的hit-testing顺序是按照UIView中Subviews的逆顺序


2、如果View的同级别Subview中有重叠的部分,则优先检查顶部的Subview,如果顶部的Subview返回nil, 再检查底部的Subview


3、Hit-Test也是比较聪明的,检测过程中有这么一点,就是说如果点击没有发生在某View中,那么该事件就不可能发生在View的Subview中,所以检测过程中发现该事件不在ViewB内,也直接就不会检测在不在ViewF内。也就是说,如果你的Subview设置了clipsToBounds=NO,实际显示区域可能超出了superView的frame,你点击超出的部分,是不会处理你的事件的,就是这么任性!


Hit-Test的检查机制如上所示,当确定了Hit-TestView时,如果当前的application没有忽略触摸事件 (UIApplication:isIgnoringInteractionEvents),则application就会去分发事件(sendEvent:->keywindow:sendEvent:)


UIView中提供两个方法用来确定hit-testing View,如下所示 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system


- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds


当一个View收到hitTest消息时,会调用自己的pointInside:withEvent:方法,如果pointInside返回YES,则表明触摸事件发生在我自己内部,则会遍历自己的所有Subview去寻找最小单位(没有任何子view)的UIView,如果当前View.userInteractionEnabled = NO,enabled=NO(UIControl),或者alpha<=0.01, hidden等情况的时候,hitTest就不会调用自己的pointInside了,直接返回nil,然后系统就回去遍历兄弟节点。简而言之,可以写成这样


Objective-C


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha &lt;= 0.01 || !self.userInteractionEnabled || self.hidden) {
        return nil;
    }
    BOOL inside = [self pointInside:point withEvent:event];
    UIView *hitView = nil;
    if (inside) {
        NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
        for (UIView *subview in enumerator) {
            hitView = [subview hitTest:point withEvent:event];
            if (hitView) {
                break;
            }
        }
        if (!hitView) {
            hitView = self;
        }
        return hitView;
    } else {
        return nil;
    }
}


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha &lt;= 0.01 || !self.userInteractionEnabled || self.hidden) {
        return nil;
    }
    BOOL inside = [self pointInside:point withEvent:event];
    UIView *hitView = nil;
    if (inside) {
        NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
        for (UIView *subview in enumerator) {
            hitView = [subview hitTest:point withEvent:event];
            if (hitView) {
                break;
            }
        }
        if (!hitView) {
            hitView = self;
        }
        return hitView;
    } else {
        return nil;
    }
}
hit-Test 是事件分发的第一步,就算你的app忽略了事件,也会发生hit-Test。确定了hit-TestView之后,才会开始进行下一步的事件分发。


我们可以利用hit-Test做一些事情,比如我们点击了ViewA,我们想让ViewB响应,这个时候,我们只需要重写View’s hitTest方法,返回ViewB就可以了,虽然可能用不到,但是偶尔还是会用到的。大概代码如下:


Objective-C


@interface STPView : UIView


@end


@implementation STPView


- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);
        button.tag = 10001;
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@&quot;Button1&quot; forState:UIControlStateNormal];
        [self addSubview:button];
        [button addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];
        
        UIButton *button2 = [UIButton buttonWithType:UIButtonTypeCustom];
        button2.frame = CGRectMake(0, CGRectGetHeight(frame) / 2, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);
        button2.tag = 10002;
        button2.backgroundColor = [UIColor darkGrayColor];
        [button2 setTitle:@&quot;Button2&quot; forState:UIControlStateNormal];
        [self addSubview:button2];
        [button2 addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];
    }
    return self;
}


- (void)_buttonActionFired:(UIButton *)button {
    NSLog(@&quot;=====Button Titled %@ ActionFired &quot;, [button titleForState:UIControlStateNormal]);
}


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == [self viewWithTag:10001]) {
        return [self viewWithTag:10002];
    }
    return hitView;
}


@end


@interface STPView : UIView
 
@end
 
@implementation STPView
 
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);
        button.tag = 10001;
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@&quot;Button1&quot; forState:UIControlStateNormal];
        [self addSubview:button];
        [button addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];
        
        UIButton *button2 = [UIButton buttonWithType:UIButtonTypeCustom];
        button2.frame = CGRectMake(0, CGRectGetHeight(frame) / 2, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);
        button2.tag = 10002;
        button2.backgroundColor = [UIColor darkGrayColor];
        [button2 setTitle:@&quot;Button2&quot; forState:UIControlStateNormal];
        [self addSubview:button2];
        [button2 addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];
    }
    return self;
}
 
- (void)_buttonActionFired:(UIButton *)button {
    NSLog(@&quot;=====Button Titled %@ ActionFired &quot;, [button titleForState:UIControlStateNormal]);
}
 
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == [self viewWithTag:10001]) {
        return [self viewWithTag:10002];
    }
    return hitView;
}
 
@end
大家可以试一试,上述代码在点击上面的按钮的时候,实际会触发下面按钮的事件,不是经常用到,但是也算是涨姿势了,这里给大家提供一个Category,来自STKit,这个category的目的就是方便的编写hitTest方法,由于hitTest方法是override,而不是delegate,所以使用默认的实现方式就比较麻烦。Category如下


Objective-C


/**
 * @abstract hitTestBlock
 *
 * @param 其余参数 参考UIView hitTest:withEvent:
 * @param returnSuper 是否返回Super的值。如果*returnSuper=YES,则代表会返回 super hitTest:withEvent:, 否则则按照block的返回值(即使是nil)
 * 
 * @discussion 切记,千万不要在这个block中调用self hitTest:withPoint,否则则会造成递归调用。这个方法就是hitTest:withEvent的一个代替。
 */
typedef UIView * (^STHitTestViewBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);
typedef BOOL (^STPointInsideBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);


@interface UIView (STHitTest)
/// althought this is strong ,but i deal it with copy
@property(nonatomic, strong) STHitTestViewBlock hitTestBlock;
@property(nonatomic, strong) STPointInsideBlock pointInsideBlock;


@end


/**
 * @abstract hitTestBlock
 *
 * @param 其余参数 参考UIView hitTest:withEvent:
 * @param returnSuper 是否返回Super的值。如果*returnSuper=YES,则代表会返回 super hitTest:withEvent:, 否则则按照block的返回值(即使是nil)
 * 
 * @discussion 切记,千万不要在这个block中调用self hitTest:withPoint,否则则会造成递归调用。这个方法就是hitTest:withEvent的一个代替。
 */
typedef UIView * (^STHitTestViewBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);
typedef BOOL (^STPointInsideBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);
 
@interface UIView (STHitTest)
/// althought this is strong ,but i deal it with copy
@property(nonatomic, strong) STHitTestViewBlock hitTestBlock;
@property(nonatomic, strong) STPointInsideBlock pointInsideBlock;
 
@end
Objective-C


@implementation UIView (STHitTest)


const static NSString *STHitTestViewBlockKey = @&quot;STHitTestViewBlockKey&quot;;
const static NSString *STPointInsideBlockKey = @&quot;STPointInsideBlockKey&quot;;


+ (void)load {
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(hitTest:withEvent:)),
                                   class_getInstanceMethod(self, @selector(st_hitTest:withEvent:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(pointInside:withEvent:)),
                                   class_getInstanceMethod(self, @selector(st_pointInside:withEvent:)));
}


- (UIView *)st_hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];
    UIView *superView = self.superview;
    while (superView) {
        [spaces appendString:@&quot;----&quot;];
        superView = superView.superview;
    }
    NSLog(@&quot;%@%@:[hitTest:withEvent:]&quot;, spaces, NSStringFromClass(self.class));
    UIView *deliveredView = nil;
    // 如果有hitTestBlock的实现,则调用block
    if (self.hitTestBlock) {
        BOOL returnSuper = NO;
        deliveredView = self.hitTestBlock(point, event, &amp;returnSuper);
        if (returnSuper) {
            deliveredView = [self st_hitTest:point withEvent:event];
        }
    } else {
        deliveredView = [self st_hitTest:point withEvent:event];
    }
//    NSLog(@&quot;%@%@:[hitTest:withEvent:] Result:%@&quot;, spaces, NSStringFromClass(self.class), NSStringFromClass(deliveredView.class));
    return deliveredView;
}


- (BOOL)st_pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];
    UIView *superView = self.superview;
    while (superView) {
        [spaces appendString:@&quot;----&quot;];
        superView = superView.superview;
    }
    NSLog(@&quot;%@%@:[pointInside:withEvent:]&quot;, spaces, NSStringFromClass(self.class));
    BOOL pointInside = NO;
    if (self.pointInsideBlock) {
        BOOL returnSuper = NO;
        pointInside =  self.pointInsideBlock(point, event, &amp;returnSuper);
        if (returnSuper) {
            pointInside = [self st_pointInside:point withEvent:event];
        }
    } else {
        pointInside = [self st_pointInside:point withEvent:event];
    }
    return pointInside;
}


- (void)setHitTestBlock:(STHitTestViewBlock)hitTestBlock {
    objc_setAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey), hitTestBlock, OBJC_ASSOCIATION_COPY);
}


- (STHitTestViewBlock)hitTestBlock {
    return objc_getAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey));
}


- (void)setPointInsideBlock:(STPointInsideBlock)pointInsideBlock {
    objc_setAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey), pointInsideBlock, OBJC_ASSOCIATION_COPY);
}


- (STPointInsideBlock)pointInsideBlock {
    return objc_getAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey));
}


@end


@implementation UIView (STHitTest)
 
const static NSString *STHitTestViewBlockKey = @&quot;STHitTestViewBlockKey&quot;;
const static NSString *STPointInsideBlockKey = @&quot;STPointInsideBlockKey&quot;;
 
+ (void)load {
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(hitTest:withEvent:)),
                                   class_getInstanceMethod(self, @selector(st_hitTest:withEvent:)));
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(pointInside:withEvent:)),
                                   class_getInstanceMethod(self, @selector(st_pointInside:withEvent:)));
}
 
- (UIView *)st_hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];
    UIView *superView = self.superview;
    while (superView) {
        [spaces appendString:@&quot;----&quot;];
        superView = superView.superview;
    }
    NSLog(@&quot;%@%@:[hitTest:withEvent:]&quot;, spaces, NSStringFromClass(self.class));
    UIView *deliveredView = nil;
    // 如果有hitTestBlock的实现,则调用block
    if (self.hitTestBlock) {
        BOOL returnSuper = NO;
        deliveredView = self.hitTestBlock(point, event, &amp;returnSuper);
        if (returnSuper) {
            deliveredView = [self st_hitTest:point withEvent:event];
        }
    } else {
        deliveredView = [self st_hitTest:point withEvent:event];
    }
//    NSLog(@&quot;%@%@:[hitTest:withEvent:] Result:%@&quot;, spaces, NSStringFromClass(self.class), NSStringFromClass(deliveredView.class));
    return deliveredView;
}
 
- (BOOL)st_pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];
    UIView *superView = self.superview;
    while (superView) {
        [spaces appendString:@&quot;----&quot;];
        superView = superView.superview;
    }
    NSLog(@&quot;%@%@:[pointInside:withEvent:]&quot;, spaces, NSStringFromClass(self.class));
    BOOL pointInside = NO;
    if (self.pointInsideBlock) {
        BOOL returnSuper = NO;
        pointInside =  self.pointInsideBlock(point, event, &amp;returnSuper);
        if (returnSuper) {
            pointInside = [self st_pointInside:point withEvent:event];
        }
    } else {
        pointInside = [self st_pointInside:point withEvent:event];
    }
    return pointInside;
}
 
- (void)setHitTestBlock:(STHitTestViewBlock)hitTestBlock {
    objc_setAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey), hitTestBlock, OBJC_ASSOCIATION_COPY);
}
 
- (STHitTestViewBlock)hitTestBlock {
    return objc_getAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey));
}
 
- (void)setPointInsideBlock:(STPointInsideBlock)pointInsideBlock {
    objc_setAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey), pointInsideBlock, OBJC_ASSOCIATION_COPY);
}
 
- (STPointInsideBlock)pointInsideBlock {
    return objc_getAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey));
}
 
@end
代码很简单,就是利用iOS的runtime能力,在hitTest执行之前,插入了一个方法。如果有看不懂的,可以参考我以前的博客 iOS面向切面编程


现在回到我们开始提出的题目,其实题目很简单,就是简单的可以把题目转换为


如果我们触摸点的坐标 point.x < 13, 我们就让hit-Test 返回NavigationController.view, 把所有的事件入口交给他,否则就返回super,该怎么处理怎么处理


这样就能满足我们的条件,即使当前的VC上面有ScrollView,但是由于点击特定区域的时候,ScrollView根本得不到事件,所以系统会专心处理NavigationController的拖拽手势,而不是ScrollView的事件,当没有点击特定区域的时候,NavigationController的手势不会触发,系统会专心处理ScrollView的事件,互不影响,大家可以尝试实现,代码量不多。


虽然iOS8新增了UIScreenEdgePanGestureRecognizer 手势,但是单纯的用这个手势无法解决当前VC上面有ScrollView的问题,有关手势方面的事件分发,之后的文章会对此进行说明,这里就不多说了。


当我们确定了HitTestView之后,我们的事件分发就正式开始了,如果hitTestView可以直接处理的,就处理,不能处理的,则交给 The Responder Chain/ GestureRecognizer。后续文章会对分发进行进一步说明。


附上一些测试查找hitTestView过程中打印的日志,可以观察一下:


Objective-C


STPWindow:[hitTest:withEvent:]
----UIView:[hitTest:withEvent:]
--------STPView:[hitTest:withEvent:]
--------UICollectionView:[hitTest:withEvent:]
------------UIImageView:[hitTest:withEvent:]
------------UIImageView:[hitTest:withEvent:]
------------STDefaultRefreshControl:[hitTest:withEvent:]
------------STPFeedCell:[hitTest:withEvent:]
------------STPFeedCell:[hitTest:withEvent:]
----------------UIView:[hitTest:withEvent:]
--------------------UIImageView:[hitTest:withEvent:]
------------------------UIImageView:[hitTest:withEvent:]
------------------------UIView:[hitTest:withEvent:]
------------------------STImageView:[hitTest:withEvent:]


STPWindow:[hitTest:withEvent:]
----UIView:[hitTest:withEvent:]
--------STPView:[hitTest:withEvent:]
--------UICollectionView:[hitTest:withEvent:]
------------UIImageView:[hitTest:withEvent:]
------------UIImageView:[hitTest:withEvent:]
------------STDefaultRefreshControl:[hitTest:withEvent:]
------------STPFeedCell:[hitTest:withEvent:]
------------STPFeedCell:[hitTest:withEvent:]
----------------UIView:[hitTest:withEvent:]
--------------------UIImageView:[hitTest:withEvent:]
------------------------UIImageView:[hitTest:withEvent:]
------------------------UIView:[hitTest:withEvent:]
------------------------STImageView:[hitTest:withEvent:]
其中—-表示View的层次结构
========

ios开发 在UiView中添加点击事件



虽然swift语言已经发布,但就目前而言,开发iosApp还是用objective c来完成,此处说的是objective c语言中的。


在app的开发过程中我们常常需要在一些非button中添加一些点击事件,来实现我们想要的效果。比如做个下拉选项,我们希望点击背景时选项视图消失,或者我们点击某个图片(uiimageView)时跳转到大图页面或者做其他操作。也许初学者会郁闷。。。这些怎么添加点击事件呢?点击事件不是只有button才能添加么?其实只要是继承 uiview的空间,你都可以手动的添加一些点击事件。


让我们看看是怎么实现的吧。


UITapGestureRecognizer*tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:selfaction:@selector(Actiondo:)];


    [uiview addGestureRecognizer:tapGesture];


其实理解起来也很简单,就是新建一个UITapGestureRecognizer,这个是点击事件,再将这个事件加到uiview中,继承uiview的一般都有addGestureRecognizer这个方法。addGestureRecognizer方法就是用于添加点击事件的。


我们再定义一个Actiondo的响应方法。


-(void)Actiondo:(id)sender{}


将我们需要的动作添加在其中就可以了。


好了,UITapGestureRecognizer的使用就是这样了。赶快试一下吧!
========

UITextView 响应 键盘的return(完成键)

http://blog.sina.com.cn/u/1509658847


UITextView <wbr>响应 <wbr>键盘的return(完成键)


UITextFieldDelegate代理里面响应return键的回调:textFieldShouldReturn:。
但是 UITextView的代理UITextViewDelegate 里面并没有这样的回调。
但是有别的方法可以实现:
UITextViewDelegate里面有这样一个代理函数:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text


这个函数的最后一个参数text代表你每次输入的的那个字,所以:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
    if ([text isEqualToString:@"\n"]){ //判断输入的字是否是回车,即按下return
        //在这里做你响应return键的代码
        return NO; //这里返回NO,就代表return键值失效,即页面上按下return,不会出现换行,如果为yes,则输入页面会换行
    }


    return YES;
}
========
http://blog.csdn.net/jianxin160/article/details/47753217
iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值