• 你真的了解UIEvent、UITouch吗?


    一:首先查看一下关于UIEvent的定义

    //事件类型
    typedef NS_ENUM(NSInteger, UIEventType) {
        UIEventTypeTouches,
        UIEventTypeMotion,
        UIEventTypeRemoteControl,
    };
    
     // 触摸事件的类型
    typedef NS_ENUM(NSInteger, UIEventSubtype) {
        
        UIEventSubtypeNone                              = 0,
        //摇晃 
        UIEventSubtypeMotionShake                       = 1,
       //播放
        UIEventSubtypeRemoteControlPlay                 = 100,
       //暂停
        UIEventSubtypeRemoteControlPause                = 101,
        //停止
        UIEventSubtypeRemoteControlStop                 = 102,
        //播放和暂停切换 
        UIEventSubtypeRemoteControlTogglePlayPause      = 103,
        //下一首
        UIEventSubtypeRemoteControlNextTrack            = 104,
        //上一首
        UIEventSubtypeRemoteControlPreviousTrack        = 105,
         //开始后退 
        UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
        //结束后退 
        UIEventSubtypeRemoteControlEndSeekingBackward   = 107,
        //开始快进 
        UIEventSubtypeRemoteControlBeginSeekingForward  = 108,
        //结束快进
        UIEventSubtypeRemoteControlEndSeekingForward    = 109,
    };
    
    
    NS_CLASS_AVAILABLE_IOS(2_0) @interface UIEvent : NSObject
    //事件类型
    @property(nonatomic,readonly) UIEventType     type NS_AVAILABLE_IOS(3_0);
    // 触摸事件的类型
    @property(nonatomic,readonly) UIEventSubtype  subtype NS_AVAILABLE_IOS(3_0);
    
    //事件的时间戳
    @property(nonatomic,readonly) NSTimeInterval  timestamp;
    
    //所有的触摸 
    - (nullable NSSet <UITouch *> *)allTouches;
    //获得UIWindow的触摸
    - (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;
    //获得UIView的触摸  
    - (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
    //获得事件中特定手势的触摸
    - (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture NS_AVAILABLE_IOS(3_2);
    
    //会将丢失的触摸放到一个新的 UIEvent 数组中,你可以用 coalescedTouchesForTouch(_:) 方法来访问
    - (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0);
    //辅助UITouch的触摸,预测发生了一系列主要的触摸事件。这些预测可能不完全匹配的触摸的真正的行为,因为它的移动,所以他们应该被解释为一个估计。
    - (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0);
    
    @end

    UIEvent是代表iOS系统中的一个事件,一个事件包含一个或多个的UITouch;UIEvent分为三类:UIEventTypeTouches触摸事件(通过触摸、手势进行触发,例如手指点击、缩放)、UIEventTypeMotion运动事件,通过加速器进行触发(例如手机晃动)、UIEventTypeRemoteControl远程控制事件通过其他远程设备触发(例如耳机控制按钮);

    知识点1:iOS中并不是所有的类都能处理接收并事件,只有继承自UIResponder类的对象才能处理事件,(如我们常用的UIView、UIViewController、UIApplication都继承自UIResponder,它们都能接收并处理事件)。在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 NS_AVAILABLE_IOS(3_0);
    运动开始时执行;
    
    (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
    运动结束后执行;
    
    (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
    运动被意外取消时执行;

    远程控制事件:

    (void)remoteControlReceivedWithEvent:(UIEvent *)event NS_AVAILABLE_IOS(4_0);
    接收到远程控制消息时执行;

    知识点2:iOS「摇一摇」功能的实现(运动事件的运用)

    - (void)viewDidLoad {  
    [super viewDidLoad];  
    // 设置允许摇一摇功能  
    [UIApplication sharedApplication].applicationSupportsShakeToEdit = YES;  
    // 并让自己成为第一响应者  
    [self becomeFirstResponder];  
    return;  
    }
    
    //摇一摇相关方法
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {  
    NSLog(@"开始摇动");  
    return;  
    }  
    
    - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {  
    NSLog(@"取消摇动");  
    return;  
    }  
    
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {  
    if (event.subtype == UIEventSubtypeMotionShake) { // 判断是否是摇动结束  
        NSLog(@"摇动结束");  
    }  
    return;  
    }

    另外:监听运动事件前提,监听对象必须成为第一响应者;在模拟器中运行时,可以通过「Hardware」-「Shake Gesture」来测试「摇一摇」功能

    知识点3:一个摇动随机图片显示的实例(运动事件的运用)

    KCImageView.m
    
    #import "KCImageView.h"
    
    #define kImageCount 3
    
    @implementation KCImageView
    
    - (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    
    if (self) {
    
    self.image = [self getImage];
    
    }
    
    return self;
    
    }
    
    #pragma mark 设置控件可以成为第一响应者
    
    - (BOOL)canBecomeFirstResponder{
    
    return YES;
    
    }
    
    #pragma mark 运动开始
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    
    //这里只处理摇晃事件
    
    if (motion == UIEventSubtypeMotionShake) {
    
    self.image = [self getImage];
    
    }
    
    }
    
    #pragma mark 运动结束
    
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    
    }
    
    #pragma mark 随机取得图片
    
    - (UIImage *)getImage{
    
    int index = arc4random() % kImageCount;
    
    NSString *imageName = [NSString stringWithFormat:@"avatar%i.png",index];
    
    UIImage *image = [UIImage imageNamed:imageName];
    
    return image;
    
    }
    
    @end
    
    KCShakeViewController.m
    
    #import "KCShakeViewController.h"
    
    #import "KCImageView.h"
    
    @interface KCShakeViewController (){
    
    KCImageView *_imageView;
    
    }
    
    @end
    
    @implementation KCShakeViewController
    
    - (void)viewDidLoad {
    
    [super viewDidLoad];
    
    }
    
    #pragma mark 视图显示时让控件变成第一响应者
    
    - (void)viewDidAppear:(BOOL)animated{
    
    _imageView = [[KCImageView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
    
    _imageView.userInteractionEnabled = true;
    
    [self.view addSubview:_imageView];
    
    [_imageView becomeFirstResponder];
    
    }
    
    #pragma mark 视图不显示时注销控件第一响应者的身份
    
    - (void)viewDidDisappear:(BOOL)animated{
    
    [_imageView resignFirstResponder];
    
    }
    
    @end

    知识点4:远程控制事件的运用

    iOS远程控制事件,是通过其他远程设备触发的(比如耳机控制按钮),iOS远程控制事件相关的只有-(void)remoteControlReceivedWithEvent:(UIEvent *)event;监听远程控制事件的前提:启动远程事件接收,调用[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];UI控件同样要求必须成为第一响应者【使用参考运动事件】,但如果是视图控制器或UIApplication,就没有要求成为第一响应者,应用程序必须是 当前音频额控制者,目前iOS7给我们的远程控制权限仅限于音频控制;

    一个关于音乐远程控制实例:

    #import "ViewController.h"
    
    @interface ViewController (){
    
    UIButton *_playButton;
    
    BOOL _isPlaying;
    
    }
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    
    [super viewDidLoad];
    
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    
    [self initLayout];
    
    }
    
    - (BOOL)canBecomeFirstResponder{
    
    return NO;
    
    }
    
    - (void)viewDidAppear:(BOOL)animated{
    
    [super viewDidAppear:animated];
    
    NSURL *url = [NSURL URLWithString:@"http://stream.jewishmusicstream.com:8000"];
    
    _player = [[AVPlayer alloc] initWithURL:url];
    
    }
    
    #pragma mark 远程控制事件
    
    - (void)remoteControlReceivedWithEvent:(UIEvent *)event{
    
    if(event.type == UIEventTypeRemoteControl){
    
    switch (event.subtype) {
    
    case UIEventSubtypeRemoteControlPlay:
    
    [_player play];
    
    _isPlaying = true;
    
    break;
    
    case UIEventSubtypeRemoteControlTogglePlayPause:
    
    [self btnClick:_playButton];
    
    break;
    
    case UIEventSubtypeRemoteControlNextTrack:
    
    NSLog(@"Next...");
    
    break;
    
    case UIEventSubtypeRemoteControlPreviousTrack:
    
    NSLog(@"Previous...");
    
    break;
    
    case UIEventSubtypeRemoteControlBeginSeekingForward:
    
    NSLog(@"Begin seek forward...");
    
    break;
    
    case UIEventSubtypeRemoteControlEndSeekingForward:
    
    NSLog(@"End seek forward...");
    
    break;
    
    case UIEventSubtypeRemoteControlBeginSeekingBackward:
    
    NSLog(@"Begin seek backward...");
    
    break;
    
    case UIEventSubtypeRemoteControlEndSeekingBackward:
    
    NSLog(@"End seek backward...");
    
    break;
    
    default:
    
    break;
    
    }
    
    [self changeUIState];
    
    }
    
    }
    
    #pragma mark 界面布局
    
    - (void)initLayout{
    
    //专辑封面
    
    UIImage *image = [UIImage imageNamed:@"wxl.jpg"];
    
    CGRect *frame = [UIScreen mainScreen].applicationFrame;
    
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
    
    imageView.image = image;
    
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    
    [self.view addSubview:imageView];
    
    //播放控制面板
    
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 480, 320, 88)];
    
    view.backgroundColor = [UIColor lightGrayColor];
    
    view.alpha = 0.9;
    
    [self.view addSubview:view];
    
    //添加播放按钮
    
    _playButton = [UIButton buttonWithType:UIButtonTypeCustom];
    
    _playButton.bounds = CGRectMake(0, 0, 50, 50);
    
    CGFloat playBtnX = view.frame.size.width/2;
    
    CGFloat playBtnY = view.frame.size.height/2;
    
    _playButton.center = CGPointMake(playBtnX, playBtnY);
    
    [self changeUIState];
    
    [_playButton addTarget:self
    
    action:@selector(btnClick:)
    
    forControlEvents:UIControlEventTouchUpInside];
    
    [view addSubview:_playButton];
    
    }
    
    #pragma mark 界面状态
    
    - (void)changeUIState{
    
    if(_isPlaying){
    
    UIImage *pauseImage = [UIImage imageNamed:@"playing_btn_pause_n.png"];
    
    UIImage *pauseImageH = [UIImage imageNamed:@"playing_btn_pause_h.png"];
    
    [_playButton setImage:pauseImage forState:UIControlStateNormal];
    
    [_playButton setImage:pauseImageH forState:UIControlStateHighlighted];
    
    }else{
    
    UIImage *playImage = [UIImage imageNamed:@"playing_btn_play_n.png"];
    
    UIImage *playImageH = [UIImage imageNamed:@"playing_btn_play_h.png"];
    
    [_playButton setImage:playImage forState:UIControlStateNormal];
    
    [_playButton setImage:playImageH forState:UIControlStateHighlighted];
    
    }
    
    }
    
    - (void)btnClick:(UIButton *)btn{
    
    if (_isPlaying) {
    
    [_player pause];
    
    }else{
    
    [_player play];
    
    }
    
    _isPlaying =! _isPlaying;
    
    [self changeUIState];
    
    }
    
    @end

    二:首先查看一下关于UITouch的定义

    //触摸事件在屏幕上有一个周期
    typedef NS_ENUM(NSInteger, UITouchPhase) {
        UITouchPhaseBegan,           //开始触摸  
        UITouchPhaseMoved,           //移动    
        UITouchPhaseStationary,      //停留
        UITouchPhaseEnded,            //触摸结束
        UITouchPhaseCancelled,       //触摸中断
    };
    
    //检测是否支持3DTouch
    typedef NS_ENUM(NSInteger, UIForceTouchCapability) {
        UIForceTouchCapabilityUnknown = 0,  //3D Touch检测失败
        UIForceTouchCapabilityUnavailable = 1,  //3D Touch不可用
        UIForceTouchCapabilityAvailable = 2  //3D Touch可用
    };
    
    NS_CLASS_AVAILABLE_IOS(2_0) @interface UITouch : NSObject
    
    //触摸产生或变化的时间戳 只读
    @property(nonatomic,readonly) NSTimeInterval      timestamp;
    //触摸周期内的各个状态
    @property(nonatomic,readonly) UITouchPhase        phase;
    //短时间内点击的次数 只读
    @property(nonatomic,readonly) NSUInteger          tapCount;   
    
    //获取手指与屏幕的接触半径 IOS8以后可用 只读
    @property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0);
    //获取手指与屏幕的接触半径的误差 IOS8以后可用 只读
    @property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0);
    
    //触摸时所在的窗口 只读
    @property(nullable,nonatomic,readonly,strong) UIWindow                        *window;
    //触摸时所在视图
    @property(nullable,nonatomic,readonly,strong) UIView                          *view;
    //获取触摸手势
    @property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);
    
    //取得在指定视图的位置
    // 返回值表示触摸在view上的位置
    // 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0,0))
    // 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
    - (CGPoint)locationInView:(nullable UIView *)view;
    //该方法记录了前一个触摸点的位置
    - (CGPoint)previousLocationInView:(nullable UIView *)view;
    
    //获取触摸压力值,一般的压力感应值为1.0 IOS9 只读
    @property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);
    
    //获取最大触摸压力值
    @property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0);
    
    @end

    知识点1:触摸时,图片移动(实例)

    - (void)viewDidLoad  
    {  
        UIImageView *image = [[UIImageView alloc] initWithFrame:CGRectMake(20.0, 50.0, 45.0, 45.0)];  
        image.image = [UIImage imageNamed:@"baby.png"];  
        image.tag = 100;  
        [self.view addSubview:image];  
      
        [super viewDidLoad];  
        // Do any additional setup after loading the view, typically from a nib.  
    }  
      
    -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event  
    {  
        UITouch *touch = [touches anyObject];  
        UIImageView *view1 = (UIImageView*)[self.view  viewWithTag:100];  
        CGPoint point = [touch  locationInView:self.view];  
        CGRect  frame = view1.frame;  
        frame.origin = point;  
        view1.frame = frame;  
    }

    知识点2:创建可以拖动的视图

    CGPoint originalLocation;  
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  
    {  
        UITouch *touch = [touches anyObject];  
        originalLocation = [touch locationInView:self.view];  
    }  
      
    -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event  
    {  
        UITouch *touch = [touches anyObject];  
        CGPoint currentLocation = [touch locationInView:self.view];  
        CGRect frame = self.view.frame;  
        frame.origin.x += currentLocation.x-originalLocation.x;  
        frame.origin.y += currentLocation.y-originalLocation.y;     
        self.view.frame = frame;  
    } 

    这里先在touchesBegan中通过[touch locationInView:self.view]获取手指触摸在当前视图上的位置,用CGPoint变量记录,然后在手指移动事件touchesMoved方法中获取触摸对象当前位置,并通过于与原始位置的差值计算出移动偏移量,再设置当前视图的位置。

  • 相关阅读:
    解决 Android SDK Manager不能下载旧版本的sdk的问题
    [置顶] 如何合并文件中的内容?
    JSTL解析——005——core标签库04
    C中的几组指针
    别动我的奶酪:CSV文件数据丢零现象及对策
    重载(overload),覆盖/重写(override),隐藏(hide)
    IOS 轻量级数据持久化 DataLite
    记录路径dp-4713-Permutation
    android 多媒体数据库详解
    Data Recovery Advisor(数据恢复顾问)
  • 原文地址:https://www.cnblogs.com/wujy/p/5820825.html
Copyright © 2020-2023  润新知