事件处理

事件处理标签主要用于了解iOS 中用户和 UI界面交互是靠怎样的机制完成的。

UIResonder

  • 只有继承了UIResonder的对象才可以接受并处理事件,这些对象被称为响应者对象
  • UIApplicationUIViewControllerUIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件

UIResponder内部提供了以下方法来处理事件:

触摸事件

1
2
3
4
5
6
7
8
9
10
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;

// 当手指在view上移动的时候
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;

// 当手指离开这个view的时候
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

// 当触摸事件被打断的时候调用(电话打入)
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

对于触摸事件,如果想要支持多手指触摸,需要设置一个属性:

上述相应触摸事件的 touches 是一个有UITouch对象的集合。关于 UITouch 的知识请点击UITouch

UIEvent常见属性

  • 事件类型
    1
    2
    @property(nonatomic,readonly) UIEventType     type;
    @property(nonatomic,readonly) UIEventSubtype subtype;
  • 事件产生的时间
    1
    @property(nonatomic,readonly) NSTimeInterval  timestamp;

加速计事件

1
2
3
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

远程控制事件

1
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;

事件的传递和传递原理

  1. 第一步:
    当发生触摸事件后,系统将会将该事件加入到一个由 UIApplication 管理的事件队列中,UIApplication 会从事件队列中取出最前面的事件,发送下去以便处理,通常发送事件给应用程序的主窗口(keyWindow),主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。

  2. 第二步:
    找到最合适的视图控件后,会调用视图控件的 上述处理事件方法 方法。

示例:

点击了绿色view:
UIApplication -> UIWindow -> 白色 -> 绿色

点击了蓝色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色

点击了黄色view:
UIApplication -> UIWindow -> 白色 ->橙色 -> 蓝色 ->黄色

如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件

一个视图无法接受触摸事件可能是以下三种情况:

  1. 不接收用户交互
    • userInteractionEnabled = NO
  2. 隐藏
    • hidden = YES
  3. 透明
    • alpha = 0.0 ~ 0.01

其中:UIImageView 的 userInteractionEnable 默认时 NO,因此 UIImageView 以及它的子控件默认时不能接受触摸事件的。

事件如何从父控件传递给子控件?(窗口如何找到最合适的视图)

  1. 首先,判断自己是否能接受触摸事件?
  2. 然后判断触摸点是不是在自己身上?
  3. 从外往里遍历子控件,重复前面的两个步骤

当一个视图接受到事件传递时,就会调用 -hitText 方法,这个方法用于判断找出最合适的 view,找到最合适的 view 后就会调用相应的处理方法。

具体代码如下:

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
31
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;

// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;

// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;

for (NSInteger 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) { // 寻找到最合适的view
return fitView;
}


}

// 循环结束,表示没有比自己更合适的view
return self;

}

hitText小练习1

核心代码:

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
31

#import "view.h"
#import "YellowView.h"
#import "GreenButton.h"

@interface view ()

@property (weak, nonatomic) IBOutlet YellowView *yellowView;

@property (weak, nonatomic) IBOutlet GreenButton *greenButton;

@end

@implementation view


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 判断触摸点在不在黄色 View 上
// 将触摸点转换坐标系
CGPoint btnPoint = [self convertPoint:point toView:self.greenButton];
if ([self.greenButton pointInside:btnPoint withEvent:event]) {

return self.greenButton;

}

return [super hitTest:point withEvent:event];
}

@end

练习2


触摸事件的处理(响应者链条)

当事件传递到最合适的 view 上时,系统的默认做法时交给父类去处理。[super touch],将事件顺着响应者链条向上处理,将事件交给上一个响应者处理。

作用:搞清楚响应者链条,可以让一个事件,多个对象处理。响应者对象:继承了UIResonder的可以处理事件的对象。

注意:如果当前 view 是控制器的 view,那么控制器就是上一个响应者。因为控制器也可以处理事件,继承UIResonder,如果当前 view 不是控制器 view,则父控件是上一个响应者。


文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2014/07/06/%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论