美文网首页
事件处理

事件处理

作者: 飘摇的水草 | 来源:发表于2023-01-04 16:38 被阅读0次
iOS事件
  • 在用户使用app过程中,会产生各种各样的事件
  • iOS中的事件可以分为3大类型
    • 触摸事件
    • 加速计事件:摇一摇
    • 远程控制事件:利用耳机控制音量
响应者对象
  • 在i0S中不是任何对象都能处理事件,只有继承了 UIResponder 的对象才能接收并处理事件。我们称之为“响应者对象”
  • UIApplicationUIViewControllerUIView 都继承自 UIResponder,因此它们都是响应者对象,都能够接收并处理事件
UIResponder

UIResponder内部提供了以下方法来处理事件,触摸事件

  • 触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withE*ent:(UIEvent *)event;
- (void)touchesCancelted:(NSSet *)ouches withivent:(UIEvent *)event;
  • 加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
  • 远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
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
UITouch
  • 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的 UITouch对象
  • 一根手指对应一个 UITouch 对象
  • UITouch的作用
    • 保存着跟手指相关的信息,比如触摸的位置、时间、阶段
  • 当手指移动时,系统会更新同一个 UITouch对象,使之能够一直保存该手指的触摸位置
  • 当手指离开屏幕时,系统会销毁相应的UITouch对象
  • UIView默认只支持单个手指触摸事件,如果要支持多个手指触摸,需要设置属性 multipleTouchEnabled 的值为YES
  • 提示:iPhone开发中,要以免使用双击事件!

UITouch的属性

  • 触摸产生时所处的窗口
@property (nonatomic,readon ly,retain) UIWindow  *window;
  • 触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView *view;
  • 短时间内点按屏幕的次数,可以根据tapCoun、判斯单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;
  • 记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;
  • 当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase  phase;

UITouch的方法

- (CGPoint)locationInView:(UIView *)view;

返回值表示触摸在view上的位置
这里返回的位置是针对view的坐标系的(以view的左上角为原点(O,0))调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置

- (CGPoint)previousLocationInView:(UIView *)view;

该方法记录了前一个触摸点的位置

实例

实现如下图所示的效果

在自定义的 View 对象里,代码如下:

@implementation RedView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%@",NSStringFromSelector(_cmd));
    
    //获取UITouch对象
    UITouch *touch = [touches anyObject];
    //获取当前点
    CGPoint currentPo = [touch locationInView:self];
    //获取上一个点
    CGPoint previewPo = [touch previousLocationInView:self];
    
    //获取x轴偏移量
    CGFloat offsetX = currentPo.x - previewPo.x;
    
    //获取y轴偏移量
    CGFloat offsetY = currentPo.y - previewPo.y;
    
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);
}

@end
UIEvent
  • 每产生一个事件,就会产生一个UIEvent对象
  • UIEvent:称为事件对象,记录事件产生的时刻和类型

常见属性

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

UIEvent还提供了相应的方法可以获得在某个view土面的触摸对象(UITouch)

当手指触摸屏幕的时候即生成一个 UITouch 对象和 UIEvent 对象

事件的产生和传递
  • 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中
  • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
  • 主窗口会在视图层次结构中 找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
  • 找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理,即:
    • touchesBegin……
    • touchesMoved……
    • touchesEnded……

UIView不能接收触摸事件的三种情况

  1. 不接收用户交互:userInteractionEnabled = NO,提示:UIImageView的userInteractionEnabled 默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的。
  2. 隐藏:hidden = YES;
  3. 透明:alpha = 0.0 ~ 0.01;
如何找到最合适的控件来处理事件?
  1. 自己是否能接收触摸事件?
  2. 触摸点是否在自己身上?
  3. 从后往前遍历子控件,重复前面的整个步骤

hitTest底层实现

  • hitTest在事件传递的时候调用,调用时机是当事件传递给控件的时候,就会调用这个方法,作用是寻找最合适的view
  • pointInside方法的作用是判断当前这个点在不在方法调用者(控件)上
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01)
    {
        return nil;
    }
    
    //判断点在不在当前控件
    if ([self pointInside:point withEvent:event])
    {
        return nil;
    }
    
    /**自己写的
    //从后往前遍历子控件
    __block UIView *targetView = nil;
    [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        CGPoint subPoint = [self convertPoint:point toView:obj];
        UIView *fitView = [obj hitTest:subPoint withEvent:event];
        if (fitView) {
            targetView = fitView;
            *stop = YES;
        }
    }];
    
    if (targetView != nil)
    {
        return targetView;
    }
     */
    
    //从后向前遍历子控件
    for (NSInteger i = [self.subviews count] - 1; i >= 0; i--)
    {
        UIView *subview = self.subviews[i];
        //把当前控件上的坐标系统转换成子控件上的坐标系
        CGPoint subPoint = [self convertPoint:point toView:subview];
        UIView *fitView = [subview hitTest:subPoint withEvent:event];
        if (fitView ) {
            //找到最合适的view
            return fitView;
        }
    }
    return self;
}
hitTest练习1

下面的图片中绿色的图层处于上方,按钮处于下方,当用户点击绿色视图和按钮重叠的区域时,要求响应的是处于下方的按钮。

实现效果.gif
  • 实现流程步骤,在自定义的绿色视图的类中,重写它的hitTest方法,然后刚当前坐标转换成按钮的,如果点击的区域在按钮的范围内,则返回按钮,否则按默认的来处理,完整的代码如下:
#import "GreenView.h"

@interface GreenView ()
//注意这里从xib或者storyBoad直接拖拽可能不能成功,需要先手写下面这段代码然后反向拖拽对应xib或者storyBoard里的控件
@property (nonatomic, weak) IBOutlet UIButton *button;
@end

@implementation GreenView

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //当前坐标系上的点转换到按钮上的点
    CGPoint btnPoint = [self convertPoint:point toView:self.button];
    //判断点在不在按钮上
    BOOL inside = [self.button pointInside:btnPoint withEvent:event];
    if (inside) {
        return self.button;
    }
    else{
        return [super hitTest:point withEvent:event];
    }
}
@end
hitTest练习2

实现如下场景,要求:

  1. 点击 “弹出对话框” 时出现新的图片
  2. 拖拽 “弹出对话框” 时新的图片也跟着拖动,
  3. 点击新出现的图片时,高亮状态下图片变化

详细的效果请参考下图

hitTest练习2.gif

完整的代码如下所示:

#import <UIKit/UIKit.h>

@interface CustomBtn : UIButton
//出现的新的图片按钮
@property (nonatomic, strong) UIButton *subBtn;
@end


#import "CustomBtn.h"

@implementation CustomBtn

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint position = [touch locationInView:self];
    CGPoint previsePoint = [touch previousLocationInView:self];
    CGFloat xOffset = position.x - previsePoint.x;
    CGFloat yOffset = position.y - previsePoint.y;
    
    CGPoint center = self.center;
    center.x += xOffset;
    center.y += yOffset;
    self.center = center;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGPoint subPoint = [self convertPoint:point toView:self.subBtn];
    if ([self.subBtn pointInside:subPoint withEvent:event])
    {
        return self.subBtn;
    }
    else{
        return [super hitTest:point withEvent:event];
    }
}

@end

#import "CustomBtn.h"
#import "ThirdViewController.h"

@interface ThirdViewController ()

@end

@implementation ThirdViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

//点击 “弹出对话框” 按钮时触发此方法
- (IBAction)customBtnClicked:(CustomBtn *)sender
{
    UIButton *subBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    [subBtn setBackgroundImage:[UIImage imageNamed:@"对话框"] forState:UIControlStateNormal];
    [subBtn setBackgroundImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
    subBtn.bounds = CGRectMake(0, 0, 150, 150);
    subBtn.center = CGPointMake(75, -75);
    [sender addSubview:subBtn];
    
    sender.subBtn = subBtn;
}
@end
事件的响应

触摸事件处理的详细过程

  • 用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件
  • 找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理
    • touchesBegan…
    • touchesMoved…
    • touchedEnded…
  • 这些touches方法的 默认做法 是将事件顺着 响应者链条 向上传递,将事件交给上一个响应者进行处理,即自己不处理(不实现 touches... 方法)而交由上一个响应者处理,个人理解的是:如果控件处理了,则不再向上抛
响应者链条示意图
  • 事件传递的完整过程
  1. 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。
  2. 调用最合适控件的touches....方法
  3. 如果调用了[super touches....];就会将事件顺着响应者链条往上传递,传递给上一个响应者
  4. 接着就会调上一个响应者的touches....方法

用下面例子来进行演示

响应者链

在该图中,YellowView 的父视图是 BlueView,而 BlueView 的父视图是 OrangeView,依次是 WhiteView,当点击 YellowView 时,会首先检测 YellowView 是否实现了 touches... 等方法,如果其没有实现则事件传给 BlueView ,如果其实现了 touches... 方法,则调用完 touches... 方法,事件结束,如果其不仅实现了 touches... 方法,并且在 touches... 方法内部调用了 [super touches...],则事件继续向上传递给 BlueView ,依次类推,利用这个特性可以做到多个视图触发同一事件。

代码如下:

#import "YellowView.h"

@implementation YellowView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);

    [super touchesBegan:touches withEvent:event];
}

@end

#import "BlueView.h"

@implementation BlueView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);

    [super touchesBegan:touches withEvent:event];
}

@end

#import "OrangeView.h"

@implementation OrangeView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);

    [super touchesBegan:touches withEvent:event];
}

@end


#import "WhiteView.h"

@implementation WhiteView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);
    
    [super touchesBegan:touches withEvent:event];
}

@end

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);
}

@end

当事件传递给白色视图以后,白色视图会继续向上传递给它的父视图,即控制器的view,因为这个视图不处理,则继续传递给当前控制器,输出结果如下所示:

事件传递输出结果
  • 如何判断上一个响应者
  1. 如果当前这个view是控制器的view,那么 控制器 就是上一个响应者
  2. 如果当前这个view不是控制器的view,那么 父控件 就是上一个响应者

相关文章

  • JS 事件

    目录 事件流 事件处理程序HTML事件处理程序DOM0级事件处理程序DOM2级事件处理程序IE事件处理程序跨浏览器...

  • react事件处理

    一,事件处理 写法:on+事件名称= {事件处理函数} 类组件触发写法on+事件名称 = 事件处理函数 ---...

  • 跨浏览器的事件处理程序

    事件处理程序有DOM0级事件处理程序、DOM2级事件处理程序,IE事件处理程序,DOM0级事件处理程序具有简单,跨...

  • App事件中心

    App事件中心,事件的的生产端和处理端分离,事件处理结果广播通知,事件状态(初始化、处理中和处理完成)管理,事件类...

  • Chapter 07. Broadcast

    阅读原文 7.1 . 理论概述 广播事件处理属于系统级的事件处理(一般事件处理是属于View级的事件处理) 一个应...

  • iOS和Flutter里的事件处理

    目录先说一下事件处理里的被处理者:事件一、iOS里的事件二、Flutter里的事件然后说一下事件处理里的处理者:响...

  • react文档——事件处理

    事件处理 React 元素的事件处理和 DOM 元素的事件处理非常相似。但也有一些语法差异: React 事件使用...

  • DOM事件的问题!

    1.事件冒泡 2.事件捕获 事件处理程序 1.HTML事件处理程序 2.DOM 0级事件处理程序 3.DOM 2级...

  • 2021-09-22 GUI(事件监听机制)

    事件监听机制组成事件源(组件)事件(Event)监听器(Listener)事件处理(引发事件后处理方式) 事件监听...

  • attachEvent和addEventListener区别

    attachEvent是IE的事件处理方法,是DOM0事件处理程序,只能在事件冒泡阶段触发。接收两个参数,事件处理...

网友评论

      本文标题:事件处理

      本文链接:https://www.haomeiwen.com/subject/qcplcdtx.html