iOS触摸事件探究

目录
一、基本概念
       1.1、UITouch
       1.2、UIEvent
       1.3、UIResponder
二、查找第一响应者
三、响应者链
四、Gesture Recognizer
       4.1、当前view添加手势
       4.2、当前view和superview同时添加手势
五、UIControl
       5.1、UIControl重写touch方法
       5.2、UIControl添加手势
       5.3、UIControl的父视图添加手势


占位图.jpg

一、基本概念

在研究触摸事件之间先看下一些重要的类。

1.1、UITouch

An object representing the location, size, movement, and force of a touch occurring on the screen.
表示发生在屏幕上的可以表示触摸的位置,大小,移动和力度的一个对象

通俗来讲就是一个手指触摸会生成一个UITouch对象,多个手指触摸会生成多个UITouch对象。

UITouch的部分声明如下:

@interface UITouch : NSObject
@property(nonatomic,readonly) NSTimeInterval      timestamp; //触摸的时间
@property(nonatomic,readonly) UITouchPhase        phase;     //触摸的阶段
@property(nonatomic,readonly) NSUInteger          tapCount;  //在一特定的时间内触摸某一个点的次数,可用来判断单击、双击、三击
@property(nonatomic,readonly) UITouchType         type;      //触摸的类型
@property(nonatomic,readonly) CGFloat             force;     //触摸力度,平均值是1.0
@property(nullable,nonatomic,readonly,strong) UIWindow     *window; //触摸发生时的window
@property(nullable,nonatomic,readonly,strong) UIView       *view; //发生触摸时的view,第一响应者
@end

每一个UITouch都会包含一个UITouchType(可以查看UITouch类定义,其内部有一个类型是UITouchType的属性type),其类型定义如下

typedef NS_ENUM(NSInteger, UITouchType) {
    UITouchTypeDirect,                       // 直接触摸屏幕产生的touch
    UITouchTypeIndirect,                     // 非直接触摸屏幕产生的touch
    UITouchTypePencil NS_AVAILABLE_IOS(9_1), // 使用触摸笔产生的touch
    UITouchTypeStylus NS_AVAILABLE_IOS(9_1) = UITouchTypePencil, // 废弃, 使用触摸笔 选项
} NS_ENUM_AVAILABLE_IOS(9_0);

每一个UITouch都会包含一个UITouchPhase(可以查看UITouch类定义,其内部有一个类型是UITouchPhase的属性phase),其类型定义如下

typedef NS_ENUM(NSInteger, UITouchPhase) {
    UITouchPhaseBegan,             // whenever a finger touches the surface.
    UITouchPhaseMoved,             // whenever a finger moves on the surface.
    UITouchPhaseStationary,        // whenever a finger is touching the surface but hasn't moved since the previous event.
    UITouchPhaseEnded,             // whenever a finger leaves the surface.
    UITouchPhaseCancelled,         // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face)
};

1.2、UIEvent

An object that describes a single user interaction with your app.
描述和app单次交互的对象。

UIEvent的部分声明如下:

@interface UIEvent : NSObject
@property(nonatomic,readonly) UIEventType     type;  //事件的类型
@property(nonatomic,readonly) UIEventSubtype  subtype;  //事件的子类型
@property(nonatomic,readonly) NSTimeInterval  timestamp; //事件发生的时间
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches; //每个事件中包含的触摸集合
@end

从上可以看出来每一个UIEvent事件可能包含多个UITouch,比如多指触摸。
每一个UIEvent都会包含一个UIEventType(可以查看UIEvent类定义,其内部有一个类型是UIEventType的属性type),其类型定义如下

typedef NS_ENUM(NSInteger, UIEventType) {
    UIEventTypeTouches,        // 触摸事件,按钮、手势等
    UIEventTypeMotion,         // 运动事件,摇一摇、指南针等
    UIEventTypeRemoteControl,  //使用遥控器或耳机线控等产生的事件。如播放、暂停等。
    UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0), // 3D touch
};

1.3、UIResponder

An abstract interface for responding to and handling events.
响应和处理事件的一个抽象接口。

响应者对象(UIResponder的实例)构成app事件处理的基础。许多关键的对象都是UIResponder的实例,比如UIApplicationUIWindow, UIViewController,以及所有的UIView.当前事件发生的时候,UIKit会自动将其派发到合适的对象来处理,这个对象就叫做第一响应者

UIResponder中有很多的事件,其中包括处理触摸事件、点按事件、加速事件、远程控制事件。如果想响应事件,那么对应的UIResponder必须重写相应事件的方法,比如如果处理触摸事件,可以重写touchesBegantouchesMovedtouchesEndedtouchesCancelled方法

UIResponder除了处理事件之外还管理着如何让未处理的事件传递到其它的responder对象。如果一个指定的responder对象未处理事件,那么它会沿着响应者链传递到另一个responder对象。当前下一个responder对象可能是superView或者ViewController
UIResponder的部分声明如下:

@interface UIResponder : NSObject <UIResponderStandardEditActions>
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
@property(nonatomic, readonly) BOOL canBecomeFirstResponder;    // default is NO
@property(nonatomic, readonly) BOOL canResignFirstResponder;    // default  is YES
@property(nonatomic, readonly) BOOL isFirstResponder;
// 处理触摸事件的主要方法,所以我们自定义事件处理时,就需要在这几个方法里面做文章。
- (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);
//点按事件
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
//加速事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
//远程控制事件
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);
@end

二、查找第一响应者

第一响应者的类型和事件的类型有关系:

事件类型 第一响应者
Touch events The view in which the touch occurred.
Press events The object that has focus.
Shake-motion events The object that you (or UIKit) designate.
Remote-control events The object that you (or UIKit) designate.
Editing menu messages The object that you (or UIKit) designate.

现在我们只关心触摸事件
查找第一响应者的原理如下:UIKit 会使用基于view的hit-testing方法来查找第一响应者。明确地说就是利用触摸发生的位置和视图层级中每个view的bounds进行比较(其实就是调用pointInside:withEvent,如果返回YES表示在这个范围内,NO则相反)。也就是说通过视图中的hitTest:withEvent方法遍历视图层级,直至找到包含触摸点的子view,那个view就是第一响应者。

  • 备注:如果触摸点在一个view 的bounds之外,那么这个view及其它的子view将会被忽略。因此,当一view的clipsToBounds=NO,如果触摸事件发生在子view上超出父视图的部分,那么hitTest:withEvent也不会将这个子view返回。

下面利用一个demo来验证一下第一响应者查找的过程。

图层.png

视图的层级如下:

Window
    └── ViewA
        ├── ViewB
        └── ViewC
            ├── ViewD
            └── ViewE

现在点击ViewD,则相应的打印如下图:

Window范围内查找
Window-->hitTest:withEvent:
Window-->pointInside:withEvent:
Window-->pointInside:withEvent:-->是否包含第一响应者:1
ViewA范围内查找
ViewA-->hitTest:withEvent:
ViewA-->pointInside:withEvent:
ViewA-->pointInside:withEvent:-->是否包含第一响应者:1
ViewC范围内查找
ViewC-->hitTest:withEvent:
ViewC-->pointInside:withEvent:
ViewC-->pointInside:withEvent:-->是否包含第一响应者:1
ViewE范围内查找
ViewE-->hitTest:withEvent:
ViewE-->pointInside:withEvent:
ViewE-->pointInside:withEvent:-->是否包含第一响应者:0
ViewE-->hitTest:withEvent:-->FirstResponder:(null)
ViewD范围内查找
ViewD-->hitTest:withEvent:
ViewD-->pointInside:withEvent:
ViewD-->pointInside:withEvent:-->是否包含第一响应者:1
ViewD-->hitTest:withEvent:-->FirstResponder:<ViewD: 0x105213720; frame = (29 78; 106 65); autoresize = RM+BM; layer = <CALayer: 0x2835e8d60>>
ViewC-->hitTest:withEvent:-->FirstResponder:<ViewD: 0x105213720; frame = (29 78; 106 65); autoresize = RM+BM; layer = <CALayer: 0x2835e8d60>>
ViewA-->hitTest:withEvent:-->FirstResponder:<ViewD: 0x105213720; frame = (29 78; 106 65); autoresize = RM+BM; layer = <CALayer: 0x2835e8d60>>
Window-->hitTest:withEvent:-->FirstResponder:<ViewD: 0x105213720; frame = (29 78; 106 65); autoresize = RM+BM; layer = <CALayer: 0x2835e8d60>>

结果分析:

  • 1.首先从Window开始,先调用hitTest:withEvent:,然后调用pointInside:withEvent:,因为触摸点确实在Window上所以pointInside:withEvent:返回了true
  • 2.然后遍历Window的子视图ViewA, 调用hitTest:withEvent:,然后调用pointInside:withEvent:,因为触摸点确实在ViewA上所以pointInside:withEvent:返回了true
  • 3.然后遍历ViewA的子视图(ViewB,ViewC),遍历采用的是从后往前(也即先遍历最后addSubview的视图,因为最后添加的视图通常情况下是在视图的最上层,苹果这里做了一个优化)。先遍历ViewC,操作同Window
  • 4.然后遍历ViewC的子视图(ViewD,ViewE)。同样,遍历采用的是从后往前。先遍历ViewE,操作同Window.这时pointInside:withEvent:返回了false,也即不包含触摸点。那么相应的hitTest:withEvent返回了null
  • 5.遍历ViewD,因为ViewD已经是一个叶节点(iOS中视图的层级是一个n杈树),其pointInside:withEvent:返回了true,与此相对应的hitTest:withEvent返回最终包含触摸点的视图,也即第一响应者。hitTest:withEvent的查找类似一个递归,所以在包含第一响应者的分支上的每一个视图中的hitTest:withEvent都返回第一个响应者。
  • 6.因为已经找到了第一响应者,所以ViewB这个分支就没必要遍历了。

官方文档明确指出,下面情况的视图不在遍历范围内:

  • 视图的hidden等于YES
  • 视图的alpha小于等于0.01
  • 视图的userInteractionEnabledNO
    也就是说在hitTest:withEvent调用过程中会进行上面三种情况的判断。

伪代码大致如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //3种状态无法响应事件
     if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil; 
    //触摸点若不在当前视图上则无法响应事件
    if ([self pointInside:point withEvent:event] == NO) return nil; 
    //从后往前遍历子视图数组 
    int count = (int)self.subviews.count; 
    for (int i = count - 1; i >= 0; i--)  { 
        // 获取子视图
        UIView *childView = self.subviews[I]; 
        // 坐标系的转换,把触摸点在当前视图上坐标转换为在子视图上的坐标
        CGPoint childP = [self convertPoint:point toView:childView]; 
        //询问子视图层级中的最佳响应视图
        UIView *fitView = [childView hitTest:childP withEvent:event]; 
        if (fitView)  {
            //如果子视图中有更合适的就返回
            return fitView; 
        }
    } 
    //没有在子视图中找到更合适的响应视图,那么自身就是最合适的
    return self;
}

在找到第一响应者之后所有的信息全部包含在一个UIEvent对象中,接下来会通过sendEvent方法直接将事件传递给第一响应者。


图片.png

那么接下来就是响应者链处理的问题了。

三、响应者链

所谓响应者链即有响应者对象组成的链条

下面是官方旧版本事件传递的过程:

响应者链.png

上图解释如下:

  • 触摸了initial view

  • 1.第一响应者就是initial view, 即initial view首先响应touchesBegan:withEvent:方法,如果其没有重写touchesBegan:withEvent:或者重写了但是调用了super方法那么事件会传递给橘黄色的view

  • 2.橘黄色的view开始响应touchesBegan:withEvent:方法,如果其没有重写touchesBegan:withEvent:或者重写了但是调用了super方法那么事件会传递给蓝绿色view

  • 3.蓝绿色view响应touchesBegan:withEvent:方法,如果其没有重写touchesBegan:withEvent:或者重写了但是调用了super方法那么事件会传递给控制器的view

  • 4.控制器view响应touchesBegan:withEvent:方法,如果其没有重写touchesBegan:withEvent:或者重写了但是调用了super方法那么事件会传递给控制器传递给了窗口

  • 5.窗口再传递给application

  • 如果app不能处理这个事件,那么这个事件将被废弃

下面是官方新版本事件传递的过程:

响应者链新.png

解释如下:

  • 如果text field未处理事件,UIKit会将事件传递给text fieldparent view--UIView,也即图中的第一个UIView,
  • 如果第一个UIView未处理事件,UIKit会将事件传递给第二个UIView
  • 如果第二个UIView未处理事件,正常情况下应该将事件传递给UIWindow。但是UIViewController也是UIResponder的子类,所以事件将传递给UIViewController
  • 如果UIViewController未处理事件,UIKit会将事件传递给第UIWindow
  • 如果UIWindow未处理事件,UIKit会将事件传递给第UIApplication,当然也会传递给是UIResponder的子类但不属于响应者链环节的delegate
  • 如果app不能处理这个事件,那么这个事件将被废弃

四、Gesture Recognizer

       这里不着重讲解手势,如果想了解可以参考官方文档,这里主要分析手势和touch事件响应先后的问题。
       手势识别器是处理视图上触摸事件和按压事件最简单的方式。可以将一个或多个手势附加在视图上。 手势识别器包含了视图上必要的处理逻辑,当检测到一个手势时,手势会派发给指定的目标,这个目标可以是view controller ,view 或app中其它的对象。

手势的模型图:

手势.png

手势识别器工作原理:
手势识别器的工作原理其实就是一个状态机。手势识别器从预设的状态中从一个状态转换到另一个状态。对于每一个状态,只要满足合适的条件就可以转换到下一步众多状态中的一个。根据是否手势是否是离散的,状态机有下面两种:
状态机.png

上图解释如下:

  • 手势识别器都是以Possible (UIGestureRecognizerStatePossible)状态为起始。
  • 手势识别器分析接收到的触摸事件,如果失败状态变为Failed(UIGestureRecognizerStateFailed)
  • 对于离散的手势如果识别成功状态变为Recognized (UIGestureRecognizerStateRecognized),至此识别结束
  • 对于连续的手势,当第一次被识别的时候状态从Possible状态转换到Began (UIGestureRecognizerStateBegan),然后从Began状态转换到Changed (UIGestureRecognizerStateChanged),当手势发生的时候开持续的从Changed状态转换到Changed状态。
  • 如果连续的手势不再满足相应的模式,那么它会 从Changed 转换到 Canceled (UIGestureRecognizerStateCancelled)状态,比如在触摸的时候,突然来了一个电话,这里就会进入 Canceled.
  • 如果连续的手势识别成功就会进入 Recognized状态,并重置状态机到Possible状态

手势和触摸事件的优先级:

Gesture Recognizers Get the First Opportunity to Recognize a Touch

手势优先获取事件

A window delays the delivery of touch objects to the view so that the gesture recognizer can analyze the touch first. During the delay, if the gesture recognizer recognizes a touch gesture, then the window never delivers the touch object to the view, and also cancels any touch objects it previously sent to the view that were part of that recognized sequence.
Window 对象会延迟将“触摸对”象发送给视图,从而让手势识别器最先对“触摸” 进行分析处理。在延迟期间,如果识别器成功识别触摸手势,window 对象就不会再将“触摸对象”传递给视图对象,并取消本应在手势序列中而且可以接受触摸事件的触摸对象

也就是说view上触摸事件的优先级要比view上的手势的优先级低。再放一张图:


图片.png

4.1、view添加手势

图层.png这个图片中,给ViewD添加一个手势,当前触摸ViewD的时候打印如下数据:

Window范围内查找

/*
省略无关代码
*/
Window-->hitTest:withEvent:-->FirstResponder:<ViewD: 0x105213720;***>
/**下面是添加手势之后打印的数据**/
ViewD -- touchesBegan
ViewD -- tapGesture
ViewD -- touchesCancelled
  • 注: 如果同一个视图如果添加多个相同的手势,经测试最后一个生效

从这里可以验证手势优先获取事件,并取消了触摸事件

4.2、view和superview同时添加手势

同时给ViewC和ViewD添加手势,执行的仍然是viewD的手势,而不会执行superview(ViewC)的手势。

/**下面是添加手势之后打印的数据**/
ViewD -- touchesBegan
ViewD -- tapGesture
ViewD -- touchesCancelled

如果不为ViewD添加手势执行的是superview(ViewC)的手势。

/**下面是添加手势之后打印的数据**/
ViewD -- touchesBegan
ViewC-- tapGesture
ViewD -- touchesEnded

注意打印数据对比

  • ViewD未添加手势,superview(ViewC)添加手势,先执行superview(ViewC)的手势,然后执行ViewD的触摸方法
  • ViewD添加手势,superview(ViewC)添加手势,执行ViewD的手势,并取消ViewD的触摸方法,并且不会执行superview(ViewC)的手势

4.3、view相关属性

cancelsTouchesInView
默认为YES。表示当手势识别器成功识别了手势之后,会通知Application取消响应链对事件的响应,并不再传递事件给hit-test view。若设置成NO,表示手势识别成功后不取消响应链对事件的响应,事件依旧会传递给hit-test view。

delaysTouchesBegan
默认为NO。默认情况下手势识别器在识别手势期间,当触摸状态发生改变时,Application都会将事件传递给手势识别器和hit-tested view;若设置成YES,则表示手势识别器在识别手势期间,截断事件,即不会将事件发送给hit-tested view。也就是说从touchesBegan后续的流程都不再调用。

delaysTouchesEnded
默认为YES。当手势识别失败时,若此时触摸已经结束,会延迟一小段时间(0.15s)再调用响应者的 touchesEnded:withEvent:;若设置成NO,则在手势识别失败时会立即通知Application发送状态为end的touch事件给hit-tested view以调用 touchesEnded:withEvent: 结束事件响应。

五、UIControl

UIControl是系统提供的能够以target-action模式处理触摸事件的控件,iOS中UIButton、UISegmentedControl、UISwitch等控件都是UIControl的子类。当UIControl跟踪到触摸事件时,会向其上添加的target发送事件以执行action。值得注意的是,UIConotrol是UIView的子类,故具有和UIResponder同样的行为。
下面是UIControl内部的四个方法,因为只能接收一个UITouch对象,所以UIControl只能是单点触摸。

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)cancelTrackingWithEvent:(nullable UIEvent *)event;

5.1、UIControl重写touch方法

image.png

代码如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    [super touchesBegan:touches withEvent:event]; //对于UIControl在其内部自动调用beginTrackingWithTouch,下面方法类似调用相应的方法
    NSLog(@"CustomButton -- touchesBegan");
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    [super touchesEnded:touches withEvent:event];
    NSLog(@"CustomButton -- touchesEnded");
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    [super touchesCancelled:touches withEvent:event];
    NSLog(@"CustomButton -- touchesCancelled");
}

打印结果:没有打印UIButton的点击方法(未调用super

CustomButton -- touchesBegan
CustomButton -- touchesEnded

如果touch方法调用super,打印结果如下:

CustomButton -- touchesBegan
CustomButton-- ButtonDidClick
CustomButton -- touchesEnded

可见touch内部自动调用了UIControl相应的方法,所以UIControl也是基于touch实现的

5.2、UIControl添加手势

结果如下:

CustomButton -- touchesBegan
CustomButton-- buttonTapGesture
CustomButton -- touchesCancelled

结果分析:手势取消了触摸事件,同样也取消了按钮的事件。如果将手势的cancelsTouchesInView 设置成false,则触摸,手势和按钮的事件同时响应,依然是手势优先执行。

5.3、UIControl的父视图添加手势

打印结果如下 :

CustomButton -- touchesBegan
CustomButton-- ButtonDidClick
CustomButton -- touchesEnded

结果分析:UIControl的事件屏蔽了父视图的手势,如果不想屏蔽父视图的手势 可将cancelsTouchesInView 设置成false,这时优先响应手势,再响应UIControl的事件

如果放cancelsTouchesInView设置成false,打印结果如下:

CustomButton -- touchesBegan
ViewB-- tapGesture
CustomButton-- ButtonDidClick
CustomButton -- touchesEnded

可以看出还是手势的优先级相对高

结论:默认情况下UIControl比其父视图上的手势识别器具有更高的事件响应优先级。

参考资料

demo
UIGestureRecognizer
Touches, Presses, and Gestures

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容

  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 5,894评论 4 26
  • 本文主要讲解iOS触摸事件的一系列机制,涉及的问题大致包括: 触摸事件由触屏生成后如何传递到当前应用? 应用接收触...
    baihualinxin阅读 1,163评论 0 9
  • 转载: https://blog.csdn.net/qq871531334/article/details/822...
    NicooYang阅读 1,539评论 0 9
  • 触摸事件的生命周期 当我们手指触碰屏幕的那一刻,一个触摸事件便产生了。经过进程间通信,触摸事件被传递到合适的应用之...
    Gintok阅读 1,258评论 0 3
  • 前言: 按照时间顺序,事件的生命周期是这样的: 事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的vi...
    reviewThis阅读 708评论 1 2