触摸事件
iOS中的事件可以分为3大类型:触摸事件,加速计事件,远程控制事件.在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”
UIView的触摸事件处理
UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件
一根或者多根手指开始触摸view,系统会自动调用view的下面方法
(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)
(void)touchesMoved:(NSSet )touches withEvent:(UIEvent )event一根或者多根手指离开view,系统会自动调用view的下面方法
(void)touchesEnded:(NSSet )touches withEvent:(UIEvent )event触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法
(void)touchesCancelled:(NSSet )touches withEvent:(UIEvent )event- 提示:touches中存放的都是UITouch对象
UITouch
- 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的
UITouch对象 - 一根手指对应一个UITouch对象
- UITouch的作用
保存着跟手指相关的信息,比如触摸的位置、时间、阶段 - 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
- 当手指离开屏幕时,系统会销毁相应的UITouch对象
- 提示:iPhone开发中,要避免使用双击事件!
UITouch的属性
触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow *window;触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView *view;短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;
UITouch的方法
(CGPoint)locationInView:(UIView *)view;
返回值表示触摸在view上的位置,这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0)),调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置(CGPoint)previousLocationInView:(UIView *)view;
该方法记录了前一个触摸点的位置
事件的产生和传递
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
- touchesBegan…
- touchesMoved…
- touchedEnded…
- 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件(掌握)
- 如何找到最合适的控件来处理事件?
- 自己是否能接收触摸事件?
- 触摸点是否在自己身上?
- 从后往前遍历子控件,重复前面的两个步骤
- 如果没有符合条件的子控件,那么就自己最适合处理
控件方法hitTest及pointInside
只要一旦点击控件就会调用
- hitTest:是控件方法
- hitTest作用:就是帮你寻找最合适的View
- hitTest什么调用:只要事件一传递给一个控件,就会调用这个这个控件的hitTest
- 点击白色 -> 产生触摸事件 -> UIApplication -> [UIWindow hitTest] -> [whiteView hitTest] return whiteView
- pointInside:就是用来判断点在不在方法调用者上
事件处理的本质和hitTest,pointInside使用。
// 事件 -> UIApplication -> UIWindow hitTest(寻找最合适view) -> WhiteView hitTest(遍历橘色,把事件传递给橘色,接着遍历绿色,把事件传递给绿色) ->
// 寻找最合适view
// point:也是方法调用者坐标系上的点
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判断下自己能否接收触摸事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2.判断下点在不在当前控件上
// 用pointInside方法判断点在不在控件上,point这个必须是方法调用者坐标系上的点
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历自己的子控件
int count = (int)self.subviews.count;
for (int i = count - 1; i >= 0; i--) {
// 取出子控件
UIView *childV = self.subviews[i];
// 把自己坐标系上点转化成子控件坐标系上点
CGPoint childP = [self convertPoint:point toView:childV];
UIView *fitView = [childV hitTest:childP withEvent:event];
// 如果找到最合适view就直接返回
if (fitView) {
return fitView;
}
}
// 4.自己就是最合适view
return self;
}
// 作用:判断point在不在方法调用者上
// point:必须是方法调用者坐标系上的点
// 什么时候调用:hitTest方法底层就会调用这个方法,判断点在不在控件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
return NO;
}
事件传递demo
效果
- 可以拖动“小黄人”按钮
- 点击按钮后,出现一张图片,点击图片切换另一张图片
注意点:在按钮上添加一个超过父控件大小的子控件,可以显示,但是点击没有效果。原因:事件 -> UIApplication -> UIWindow hitTest(寻找最合适view) -> 按钮 hitTest(不是点击在父控件按钮上,所以没有作用)
在不做处理下,点击图片按钮会没有任何反应效果
代码
- 自定义按钮,点击后添加子控件
// 小黄人
- (IBAction)btnClick:(PopButton *)sender {
// 创建对话框按钮
UIButton *chatView = [UIButton buttonWithType:UIButtonTypeCustom];
[chatView setBackgroundImage:[UIImage imageNamed:@"小黄人1"] forState:UIControlStateNormal];
[chatView setBackgroundImage:[UIImage imageNamed:@"小黄人2"] forState:UIControlStateHighlighted];
[chatView sizeToFit];
chatView.center = CGPointMake(chatView.bounds.size.width * 0.5, -chatView.bounds.size.height * 0.5);
sender.chatView = chatView;
[sender addSubview:chatView];
}
- 自定义按钮中的实现
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 转化坐标系
CGPoint chatP = [self convertPoint:point toView:_chatView];
// 判断下点在不在chatView
if ([_chatView pointInside:chatP withEvent:event]) {
return _chatView;
}else{
return [super hitTest:point withEvent:event];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// 获取UITouch对象
UITouch *touch = [touches anyObject];
// touch.v
// 获取当前点
CGPoint curP = [touch locationInView:self];
// 获取上一个点
CGPoint preP = [touch previousLocationInView:self];
// 获取当前手指偏移量
CGFloat offsetX = curP.x - preP.x;
CGFloat offsetY = curP.y - preP.y;
// 移动按钮
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}