内容简介:iOS 的事件分为三种,Tip:在模拟器中,按住option可以两根手指操作。同时按住option+shift可以移动两根手指。触摸事件可以分为两部分——传递和响应。
iOS 的事件分为三种, 触摸事件(Touch Event) 、 加速器事件(Motion Events) 、 远程遥控事件(Remote Events) 。这些事件对应的类为UIResponder。本文只探究触摸事件。
Tip:在模拟器中,按住option可以两根手指操作。同时按住option+shift可以移动两根手指。
触摸事件的处理
触摸事件可以分为两部分——传递和响应。
传递:系统把该事件传到最适合响应的对象。
响应:最适合响应的对象可以不响应,转给别的对象响应。
传递
我们偶尔会遇到显示一个小列表选择。这时候小列表超出了父view的范围。这时点击B是达不到预期效果的。
下面还是通过一份Demo来学习。
如图,父view(FirstView红色),子view(SecondView黄色)。触摸点在A区域,父view响应;触摸点在B区域,子view响应;触摸点在C区域,默认子view是不响应的。我们来实现让子view响应C区域事件。
传递步骤:
(有个有趣的地方,UIApplication和AppDelegate也继承于UIResponder)
简单地说,自下而上。UIResponder -> UIApplication -> UIWindow -> UIViewController -> UIView(父view一直遍历到子view,同层的view按后添加的view先遍历)。其遵循的规则如下:
- 自己是否能接收触摸事件? 不能接收的情况有三种 一、userInteractionEnabled = NO 二、 hidden = YES 三、 alpha = 0.0 ~ 0.01
- 触摸点是否在自己身上?
- 从后往前遍历子控件,重复前两个步骤。 若父控件不能接收触摸事件,不会传递给子控件。
- 如果没有符合条件的子控件,那么就自己最适合处理。
在https://juejin.im/post/5b614924f265da0f774ac7be借了两张图能更直观地理解传递过程。
当事件传递给当前view时,当前view会调用 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 方法。寻找最适合的view。
返回谁,谁就是最合适的view,响应事件调用touches方法。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [super hitTest:point withEvent:event];
}
复制代码
Demo中,点击了C区域,传递给控制器view后,满足1.2条件,然后传递给红色view。红色view满足1,但不满足2所以不符合。最终控制器view成为最适合的view。因此我们还要修改 触摸点是否在自己身上的方法,来让事件传递给黄色view。
在红色view中实现该方法后,就能满足条件2,从而把事件传递给黄色view。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint secondViewPoint = [self convertPoint:point toView:self.secondView];
if ([self.secondView pointInside:secondViewPoint withEvent:event]) {
return YES;
}
return [super pointInside:point withEvent:event];
}
复制代码
最终,黄色view能满足条件1.2,且没有更适合的子控件,所以成为了最适合的view。Demo的目的也就达成了。
响应
先了解有关的类,然后通过一个Demo来熟悉它们的使用。
UIResponder
@interface UIResponder : NSObject <UIResponderStandardEditActions> @property(nonatomic, readonly, nullable) UIResponder *nextResponder; - (nullable UIResponder*)nextResponder; @property(nonatomic, readonly) BOOL canBecomeFirstResponder; // default is NO - (BOOL)canBecomeFirstResponder; // default is NO - (BOOL)becomeFirstResponder; @property(nonatomic, readonly) BOOL canResignFirstResponder; // default is YES - (BOOL)canResignFirstResponder; // default is YES - (BOOL)resignFirstResponder; @property(nonatomic, readonly) BOOL isFirstResponder; - (BOOL)isFirstResponder; // 触摸事件方法 // 手指触摸 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; // 触摸时移动 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; // 手指离开屏幕 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; // 触摸状态下被系统事件(如电话等打断) - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1); @end 复制代码
触摸事件方法中有两个参数(NSSet<UITouch *> *)touches和(UIEvent *)event。
UITouch
- UITouch对象记录 触摸的位置、时间、阶段。
- 一根手指对应一个UITouch对象。
- 手指移动时,系统会更新同一个UITouch对象。
- 手指离开屏幕时,UITouch对象被销毁。
@interface UITouch : NSObject
// 触摸产生时所处的窗口
@property (nonatomic, readonly, retain) UIWindow *window;
// 触摸产生时所处的视图
@property (nonatomic, readonly, retain) UIView *view;
// 短时间内点按屏幕的次数
@property (nonatomic, readonly) NSUInteger tapCount;
// 记录了触摸事件产生或变化的时间,单位:秒
@property (nonatomic, readonly) NSTimeInterval timestamp;
// 当前触摸事件所处的状态
@property (nonatomic, readonly) UITouchPhase phase;
typedef NS_ENUM(NSInteger, UITouchPhase) {
UITouchPhaseBegan, //(触摸开始)
UITouchPhaseMoved, // (接触点移动)
UITouchPhaseStationary, // (接触点无移动)
UITouchPhaseEnded, // (触摸结束)
UITouchPhaseCancelled, // (触摸取消)
};
// 返回触摸在view上的位置
// 相对view的坐标系
// 如果参数为nil,返回的是在UIWindow的位置
- (CGPoint)locationInView:(nullable UIView *)view;
// 返回上一个触摸点的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;
@end
复制代码
UIEvent
每产生一个事件,就会产生一个UIEvent对象。记录事件产生的时刻和类型。本文探究的都是触摸事件。
@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; @end 复制代码
接下来通过一个Demo来熟悉上面提到的类。
新建一个view,在.m文件中打入以下代码,手指拖拽着该view移动。
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
NSLog(@"%s", __func__);
// 因为只有一根手指,所以用anyObject
UITouch *touch = [touches anyObject];
// 获取上一点
CGPoint previousPoint = [touch previousLocationInView:self];
// 获取当前点
CGPoint currentPoint = [touch locationInView:self];
// 计算偏移量
CGFloat offsetX = currentPoint.x - previousPoint.x;
CGFloat offsetY = currentPoint.y - previousPoint.y;
// view平移
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
复制代码
响应过程
- 响应链
简单地说,传递到最合适的view后,如果有实现 touches 方法那么就由此 View 响应,如果没有实现,那么就会 自下而上 ,传递给他的下一个响应者【子view -> 父view,控制器view -> 控制器-> UIWindow -> UIApplication -> AppDelegate】。
由这两张图,我们就可以知道每个 UIResponder对象 的 nextResponder 指向谁。
通过 touches 方法,虽然能实现响应触摸事件,但对开发还是不友好,原因有以下三个:
- 要自定义view。
- 还要在实现文件中实现 touches 方法,由此在让外部监听到实现文件中的触摸事件,增强了耦合度。
- 不容易区分用户的具体手势行为。实现长按手势都能折腾。 UITouch如何判断长击啊??
所以苹果推出了 UIGestureRecognizer 手势识别器。对常用的手势进行了封装。
手势
手势识别和触摸事件是两个独立的概念。
UIGestureRecognizer简介
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible,
UIGestureRecognizerStateBegan,
UIGestureRecognizerStateChanged,
UIGestureRecognizerStateEnded,
UIGestureRecognizerStateCancelled,
UIGestureRecognizerStateFailed,
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};
@interface UIGestureRecognizer : NSObject
@property(nonatomic,readonly) UIGestureRecognizerState state;
@property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate;
@end
复制代码
UIGestureRecognizer是一个抽象类,使用它的子类才能处理具体的手势。
UITapGestureRecognizer(敲击) UILongPressGestureRecognizer(长按) UISwipeGestureRecognizer(轻扫) UIRotationGestureRecognizer(旋转) UIPinchGestureRecognizer(捏合,用于缩放) UIPanGestureRecognizer(拖拽) 复制代码
手势的使用
- 点按手势
UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[self.view addGestureRecognizer:tapGes];
复制代码
- 长按手势。
- (void)viewDidLoad {
[super viewDidLoad];
// 创建手势
UITapGestureRecognizer *longPressGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGes:)];
// 添加手势
[view addGestureRecognizer:longPressGes];
}
// 长按手势分状态,长按移动时,也会调用
- (void)longPressGes:(UILongPressGestureRecoginzer *)longPressGes {
if (longPressGes.state == UIGestureRecognizerStateBegan) {// 长按开始
} else if (longPressGes.state == UIGestureRecognizerStateChanged) {// 长按移动
} else if (longPressGes.state == UIGestureRecognizerStateEnded) {// 长按结束
}
复制代码
- 轻扫手势 默认是向右轻扫手势。如果要向左轻扫,需要设置轻扫方向。
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGes:)];
//注意点:一个轻扫手势只能对应一个方向,不要用或。
// 要多个方向就创建多个手势。
swipe.direction = UISwipeGestureRecognizerDirectionLeft;
/*
typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
UISwipeGestureRecognizerDirectionRight = 1 << 0,
UISwipeGestureRecognizerDirectionLeft = 1 << 1,
UISwipeGestureRecognizerDirectionUp = 1 << 2,
UISwipeGestureRecognizerDirectionDown = 1 << 3
};
*/
[self.view addGestureRecognizer:swipe];
复制代码
- 拖拽手势
上面的Demo中提到的平移,需要获取上一个点和当前点计算偏移量。拖拽手势内部有方法能直接获取相对于最原始的点的偏移量。
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:pan];
}
- (void)pan:(UIPanGestureRecognizer *)pan {
// 获取偏移量
CGPoint transP = [pan translationInView:self.view];
self.view.transform = CGAffineTransformTranslate(self.view, transP.x, transP.y);
// 清0
[pan setTranslation:CGPointMake(0, 0) inView:self.view];
}
复制代码
- 旋转手势
同理,旋转手势内部有方法能直接获取相对于最原始的点的旋转量。
- (void)viewDidLoad {
[super viewDidLoad];
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
[self.view addGestureRecognizer:rotation];
}
- (void)rotation:(UIRotationGestureRecognizer *)rotationGes {
// 获取旋转角度(已经是弧度)
CGFloat rotation = rotationGes.rotation;
self.view.transform = CGAffineTransformRotate(self.view.transform, rotation);
// 清0
[rotationGes setRotation:0.f];
}
复制代码
- 捏合手势
同理,捏合手势内部有方法能直接获取相对于最原始的缩放比例。
- (void)viewDidLoad {
[super viewDidLoad];
UIPinchGestureRecognizer *rotation = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
[self.view addGestureRecognizer:rotation];
}
- (void)pinch:(UIPinchGestureRecognizer *)pinchGes {
// 放大,缩小
CGFloat scale = pinchGes.scale;
self.view.transform = CGAffineTransformScale(self.view.transform, scale, scale);
// 清0
[pinchGes setScale:0];
}
复制代码
手势的常用代理方法
// called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch // 在touchesBegan之前,是否允许该手势接收事件 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch; // called before pressesBegan:withEvent: is called on the gesture recognizer for a new press. return NO to prevent the gesture recognizer from seeing this press // 在touchesBegan之前,是否允许该手势接收事件 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press; // 是否允许同时支持多个手势 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; 复制代码
大家应该试过视频左边手势调亮度,右边调音量。就可以在代理方法中实现。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// 获取当前的点
CGPoint curP = [touch locationInView:view];
// 判断在左边还是右边
if (curP.x > view.bounds.size.width * 0.5) {// 在左边
} else {// 在右边
}
return YES;
}
复制代码
手势默认是不能同时进行的(例如上面的旋转和捏合手势),如果要同时识别,需要实现代理方法。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
复制代码
参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- TensorFlow 实现手势识别
- Flutter 手势密码控件
- Flutter学习指南:交互、手势和动画
- iOS – 几种常见手势的简单使用[原创]
- 使用Flutter仿写TikTok的手势交互(二)
- 超级小的web手势库AlloyFinger发布
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
新内容创业:我这样打造爆款IP
南立新、曲琳 / 机械工业出版社 / 2016-5-10 / 39.00
这是个内容创业爆棚的时代,在采访几十家内容创业公司,与一线最优秀的创业者独家对话之后,作者写作了这本书,其中包括对这个行业的真诚感触,以及希望沉淀下来的体系化思考。 本书共分三个部分讲述了爆红大号的内容创业模式和方法。其中第一部分,讲述了新的生产方式,即内容形态发展的现状--正在被塑造;第二部分,讲述了新的盈利探索,即从贩卖产品到贩卖内容的转变,该部分以多个案例进行佐证,内容翔实;第三部分,......一起来看看 《新内容创业:我这样打造爆款IP》 这本书的介绍吧!