事件的产生、传递

事件传递、响应者链条

2016-08-04  本文已影响266人  CoderZb

UITouch的解释及作用


UITouch的方法

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

- (CGPoint)previousLocationInView:(UIView *)view;
该方法记录了前一个触摸点的位置


UITouch的属性

触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow    *window;

触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView      *view;


UIEvent的解释及作用


UIEvent的属性

@property(nonatomic,readonly) UIEventType     type;
@property(nonatomic,readonly) UIEventSubtype  subtype;
@property(nonatomic,readonly) NSTimeInterval  timestamp;


touches和event参数

一次完整的触摸过程,会经历3个状态:
触摸开始:- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
触摸移动:- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
触摸结束:- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
触摸取消(可能会经历):- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event


事件的产生和传递


触摸事件处理的详细过程


响应者对象


UIResponder内部提供了以下方法来处理事件.因为UIApplication、UIViewController、UIView都继承UIResponder,所以这三个都能拥有以下方法(事件)
触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(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不接收触摸事件的三种情况


触摸事件的传递


事例+附图

16-02.png
16-03.png
16-04.png

精华总结1:

精华总结2(结合总结1):

16-06.png 16-05.png

HitTest方法底层实现

//什么时候调用:当一个事件传递给当前View时调用
//作用:寻找最适合的View
//返回值:找到的最适合的View,如果没有找到最适合的View,返回是一个nil.
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    //1.判断当前View能否接收事件
    if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
        return nil;
    }
    //2.判断当前点在不在View身上
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
    //3.从后往前遍历自己的子控件.让自己的子控件寻找最适合的View;
    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];
        if (fitView) {
            return fitView;
        }
    }
    //4.没有找到比自己更适合的view,那么它自己就是最适合的view
    return self;
    
    
}
//什么时候调用:在hitTest方法当中调用
//作用:判断当前点在不在方法调用者身上.
//返回值:如果返回yes,在View身,如果 为NO,不在View身上.
//传入的点point,必须得与方法调用者在同一个坐标系(即point是父控件中的坐标,必须转换成子控件身上的坐标,即坐标原点要由父控件变成子控件的坐标原点,唯一不变的是坐标代表的距离不变,这个距离要和当前子控件的宽高进行比较,如果都小于的话,才能证明这个带你在子控件身上).
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//    return YES;
//}
@end

hitTest方法练习


CharBtn.h文件

#import <UIKit/UIKit.h>

@interface ChatBtn : UIButton

/** <#注释#>*/
@property (nonatomic, weak) UIButton *popBtn;

@end

CharBtn.m文件

#import "ChatBtn.h"

@implementation ChatBtn
//当第一次点击"弹出对话框"时,按照事件的传递过程:APPLication--->UIWindow--->"弹出对话框"按钮,执行到"弹出对话框"按钮时候,所以执行"弹出对话框"重写的hitTest方法(重写只为拦截之用,但还是会执行btnclick方法),在这个方法里面进行判断(A,B,C,D步骤),结果返回系统的做法return [super hitTest:point withEvent:event];所以跳出hitTest方法之后,就会执行btnclick方法.在里面创建一个btn控件并添加到父控件"弹出对话框",并把这个控件赋值给ChatBtn类型的对象sender的popBtn属性,从而popBtn就拿到了btn,拥有了btn的图片以及尺寸存储在内存中
//第一次点击"弹出对话框"时打印两行null的原因: 第一行:因为没有给self.popBtn赋值,所以self.popBtn为null.第二行:self.popBtn为null,返回的是一个方法,所以再次执行这个重写的方法就返回了,打印第二行null。

//第二次点击"对话框",按照事件的传递过程:APPLication--->UIWindow--->"弹出对话框"按钮,执行到"弹出对话框"按钮时候,"弹出对话框"的hitTest:方法同样会进行拦截(虽然点击的是"对话框",但是因为事件是有传递过程的,每传递到一个控件时,都会执行控件的hitTest方法),self.popBtn此时已经被对话框(小孩)btn赋值,所以就会执行return self.popBtn;就会执行“对话框”按钮的响应事件,即点击“对话框”按钮有反应(popBtn里面之前已经存储了不惦记btn的图片和点击btn时图片的状态以及尺寸等).注意:不会来到btnClick方法,因为你点击的是"对话框“按钮(点在对话框"按钮身上).如果返回的是系统原有的做法return [super hitTest:point withEvent:event];,那肯定是执行btnClick方法(就相当于没重写htiTest方法,即:点击谁,就执行谁的btnClick)。


//如果 一个子控件超出父控件的大小, 默认情况下是不能处理的事件.
//点击"弹出对话框"按钮的子控件是 "对话框"按钮.  并且子控件超出了父控件的范围

//精华:点击对话框按钮,事件传递过程:APPLication--->UIWindow--->按钮,执行到按钮时,在按钮的hitTest方法中先判断出按钮能接收事件,但是点不在按钮身上(因为子控件超出了父控件的大小,并且当前点是父控件未包含的区域,)所以不会执行第三步(不会从后往前遍历按钮的子控件,),就会执行第四步:没有符合条件的子控件,那么就自己最适合处理,所以"弹出对话框"按钮自己处理

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    //通过sender.popBtn = btn;拿到对话框按钮btn,在这个类中用self.popBtn表示
    //point是点击"对话框"按钮中的某个点(当然第一次点击"弹出对话框"时,point指的就是那时的点,第二次点击时,才是“对话框”按钮的点)
    
    
     NSLog(@"%@--",self.popBtn);
    //如果点在自己的子控件上面.让子控件处理事件.
    if (self.popBtn) {
        //把当前点转换成"对话框"坐标系上面的点
        CGPoint popBtnP = [self convertPoint:point toView:self.popBtn];
        if ( [self.popBtn pointInside:popBtnP withEvent:event]) {//判断点在不在对话框"按钮身上
            return self.popBtn;
        }else {
            return [super hitTest:point withEvent:event];//点不在对话框"按钮身上,保持系统原有的做法
        }
    }
    
    return   [super hitTest:point withEvent:event];
}

//
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //获取UITouch
    UITouch *touch = [touches anyObject];
    //获取当前手指的点
    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);
}



@end

ViewController.h文件

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController


@end

ViewController.m文件

#import "ViewController.h"
#import "ChatBtn.h"

@interface ViewController ()

@end

@implementation ViewController

//重:因为"弹出对话框"按钮已经和ChatBtn绑定,所以点击"弹出对话框",传递的sender的类型是CharBtn类型
- (IBAction)btnClick:(ChatBtn *)sender {
    
    //添加一个子控件
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setImage:[UIImage imageNamed:@"对话框"] forState:UIControlStateNormal];
    //UIControlStateHighlighted可以添加图片,UIControlStateSelected不可以添加图片,只能被选中
    [btn setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
    btn.frame = CGRectMake(50, -80, 100, 80);
    //btn是UIButton类型的指针,popBtn也是UIButton类型的指针,所以可以赋值
    sender.popBtn =btn;
    //sender.popBtn =(ChatBtn *)btn;
    
    [sender addSubview:btn];
}
@end

Main.storyboard文件

16-08.png

触摸事件处理的详细过程


事件响应代码

//重写系统的方法--->保持系统默认做法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    [super touchesBegan:touches withEvent:event];//保持系统默认做法
}

//重写系统的方法--->处理事件,没有保持系统默认做法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@---touch",[self class]);   //处理事件(打印当前类),没有保持系统默认做法
 

}

//重写系统的方法--->处理事件,并保持系统默认做法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@---touch",[self class]);//处理事件(打印当前类)
    [super touchesBegan:touches withEvent:event];//保持系统默认做法,即顺着响应者链条传递给上一个响应者(父类)。
}

事件的传递和响应过程(看ABCDEF): 传递:在hitTest方法中找到适合的view(A,B,C,D) 响应:在toucherBegan方法中处理事件(E或F)

16-07.png

UIGestureRecognizer(手势识别器)

//是否允许支持多个手势
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

    return NO;
}


//旋转,缩放,位移中:  
//第一个参数是上次(之前)的位置
//第二个参数(第三个参数)是距离上次(之前)位置的偏移量
//等号左边的self.imageV.transform是(这次)现在的位置(现在的位置是根据右边的参数计算出来的位置)

//旋转
 self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, rotation.rotation);
 
 //缩放
 self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pinch.scale, pinch.scale);
 
 //位移
 self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y);

手势识别

#import "ViewController.h"

@interface ViewController ()<UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageV;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
   self.imageV.userInteractionEnabled = YES;

    [self panGes];
    
}

//是否允许支持多个手势
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

    return NO;
}

//拖动手势
- (void)panGes {
    //1.创建手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    //2.添加手势
    [self.imageV addGestureRecognizer:pan];
    
}


//当拖动时调用
- (void)pan:(UIPanGestureRecognizer *)pan {
    
    //NSLog(@"%s",__func__);
    //获取偏移量(相对于最原始的值),用transP表示。例如偏移量为100(相对于0来说)(其实是有x和y值)
    //pan.view是当前手势的所在view。所在的view是UIImageView。所以pan.view等价于self.imageV;
    CGPoint transP = [pan translationInView:pan.view];
    NSLog(@"transP=%@",NSStringFromCGPoint(transP));//1

    //CGAffineTransformTranslate的第一个参数是记录上次的位置,第二个参数和第三个参数是偏移量x和偏移量y,只有知道上次的位置和偏移量,才能知道下次的位置
    //如果屏蔽2的代码,当你拖动图片的时候,其实系统底层已经记录了你拖动到了什么位置,只是不显示。通过1的打印就可以知道,只是我们通过2将拖动到的位置显示了出来,仅此而已
    self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y);//2
    
//复位(相对于上一次的操作) ,把偏移量为1进行清零,那么下次获取偏移量时,就相对于0来说,如果不清零,下次真实的偏移量若为200时,就会走100+200=300的距离,但是实际的偏移量为200
    [pan setTranslation:CGPointZero inView:pan.view];
}


@end
上一篇 下一篇

猜你喜欢

热点阅读