首页 > 代码库 > 你真的了解UIEvent、UITouch吗?

你真的了解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;}@endKCShakeViewController.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:selfaction:@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,       //触摸中断};//检测是否支持3DTouchtypedef 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方法中获取触摸对象当前位置,并通过于与原始位置的差值计算出移动偏移量,再设置当前视图的位置。

你真的了解UIEvent、UITouch吗?