MENU

iOS 事件处理

April 9, 2016 • Read: 925 • Codes

事件(UIEvent)是一个对象,被发送给 Application,包含了一些信息来告诉我们的程序用户干了些什么。事件在 iOS 中主要包含3种:MultiTouch Events、Accelerometer Events、Remote Control Events。

MultiTouch Events

用户触摸屏幕产生的事件或 Smart Pencil 接触屏幕产生的事件,如点击、捏合等等

Accelerometer Events

传感器产生的事件,如用户摇晃手机等。

Remote Control Events

使用遥控器或耳机线控等产生的事件。如播放、暂停等。

事件的响应及传递

当一个事件产生后,首先会发给 UIApplication 对象,UIApplication 对象会查找当前应用的keyWindow对象,然后将事件发送给它。keyWindow对象收到消息后,通常情况下不会自己处理事件,而是将事件中的触摸事件提取出来,然后调用-[UIResponder touches*:withEvent:]系列方法交给 UIResponder 处理。

我们常见的UIWindowUIViewControllerUIView都是UIResponder的子类。

UIResponder 提供了一下一些方法,并提供了基础实现。

// UIResponder的touches系列方法(MultiTouch Events)

// 手指开始点击屏幕
- (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:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;


- (void)touchesEstimatedPropertiesUpdated:(NSSet * _Nonnull)touches NS_AVAILABLE_IOS(9_1);

其他的还有:

// Accelerometer Events
// 加速度感应器事件
- (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);

// 按压事件(如iPhone 6s/6s Plus及以上机型的3D Touch)
- (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);

// Remote Control Events
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);

既然我们UIWindowUIViewControllerUIView都是UIResponder的子类,那么当一个事件到来后,keyWindow对象会发送给谁来响应呢?

在这里要讲一下 Responder Chain 的概念:

每一个UIResponder对象都不是孤立的对象,而是(由系统将他们)串成了一个链条。第一个UIResponder对象即为First Responder,而后通过nextResponder属性指向下一个UIResponder对象。

- (nullable UIResponder*)nextResponder;

First Responder则由如下方法管理:

// default is NO
- (BOOL)canBecomeFirstResponder;

- (BOOL)becomeFirstResponder;

// default is YES
- (BOOL)canResignFirstResponder;

- (BOOL)resignFirstResponder;

- (BOOL)isFirstResponder;

所以,事件将交给 First Responder 来处理,如果 First Responder 不响应事件,则按照 Responder Chain 依次将进行。

但是,MultiTouch Events 会略有不同,事件的响应顺序跟上面一样,但用户的触摸事件可能会改变 First Responder。

在每一个UIView中,都有:

// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;

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

所以,当用户与屏幕交互后(如touchesBegantouchesMovedtouchesEnded等),会从当前(全屏)视图的最下面一个 View 开始,依次执行hitTest方法。

默认实现hitTest方法会调用pointInside方法询问当前用户交互的坐标是否在自己bounds里面,如果是,则继续遍历其所有subView,递归询问每个pointInsideYES的 subView。所以,如果一个 View 的pointInside方法返回NO,那么其所有 subView 将被略过。

所以,如果一个 View 的clipsToBounds值为默认的NO,并且其有子类的边界超出了其 Bounds 范围,如果不重写pointInside方法,那么在其 Bounds 范围之外的区域用户是点不到的。

最终会找到距离屏幕最近(subView 子树最末端)的 View,并返回这个 View。

然后询问其是否可以成为First Responder,如果可以,则使其成为First Responder

在此之后,如果其不响应事件,则按照之前所说的按照 Responder Chain 依次询问,直到某一个UIResponder响应事件。

通常情况下,事件会传递给 superView,superView 继续往上传递。如果其中某个 View 有 ViewController 指向此 View,则会将事件传递给 ViewController,然后再传递给其 superView。直到传递给 UIWindow 对象,如果 UIWindow 依然不处理此事件,则会传给 UIApplication 对象。到此,整个事件结束。

响应事件

想要响应什么事件,就实现对应的方法就可以了。

例如想要响应触摸开始事件,实现如下方法就可以了:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
UITouch
// 触摸事件的坐标
- (CGPoint)locationInView:(nullable UIView *)view;

// 上一次触摸事件的坐标
- (CGPoint)previousLocationInView:(nullable UIView *)view;

// 时间戳
@property(nonatomic,readonly) NSTimeInterval      timestamp;

@property(nonatomic,readonly) 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)
};
*/

// touch down within a certain point within a certain amount of time
@property(nonatomic,readonly) NSUInteger          tapCount;   

// 触摸类型,如手指在屏幕上点击、间接地(非屏幕)触摸、使用触控笔Touch
@property(nonatomic,readonly) UITouchType         type NS_AVAILABLE_IOS(9_0);

// Force of the touch, where 1.0 represents the force of an average touch
// 压力力度
@property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);

//触控笔倾斜角度
@property(nonatomic,readonly) CGFloat altitudeAngle NS_AVAILABLE_IOS(9_1);

//更多地属性请查看UITouch类

UIView要支持多点触控,需要将multipleTouchEnabled设为YES,默认为NO

UIEvent
//
@property(nonatomic,readonly) UIEventType     type NS_AVAILABLE_IOS(3_0);
/*
typedef NS_ENUM(NSInteger, UIEventType) {
    UIEventTypeTouches, //触摸
    UIEventTypeMotion, //运动(加速度传感器等)
    UIEventTypeRemoteControl, //
    UIEventTypePresses //按压(3D Touch等) NS_ENUM_AVAILABLE_IOS(9_0),
};
*/

@property(nonatomic,readonly) UIEventSubtype  subtype NS_AVAILABLE_IOS(3_0);
/*
typedef NS_ENUM(NSInteger, UIEventSubtype) {
    // available in iPhone OS 3.0
    UIEventSubtypeNone                              = 0,
    
    // for UIEventTypeMotion, available in iPhone OS 3.0
    // 晃动手机
    UIEventSubtypeMotionShake                       = 1,
    
    // for UIEventTypeRemoteControl, available in iOS 4.0
    UIEventSubtypeRemoteControlPlay                 = 100,
    UIEventSubtypeRemoteControlPause                = 101,
    UIEventSubtypeRemoteControlStop                 = 102,
    UIEventSubtypeRemoteControlTogglePlayPause      = 103,
    UIEventSubtypeRemoteControlNextTrack            = 104,
    UIEventSubtypeRemoteControlPreviousTrack        = 105,
    UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
    UIEventSubtypeRemoteControlEndSeekingBackward   = 107,
    UIEventSubtypeRemoteControlBeginSeekingForward  = 108,
    UIEventSubtypeRemoteControlEndSeekingForward    = 109,
};
*/

@property(nonatomic,readonly) NSTimeInterval  timestamp;
Tags: iOS, iOS开发
Archives QR Code Tip
QR Code for this page
Tipping QR Code
Leave a Comment