transform
特点:
// 使用Make方法,它是相对 最原始的位置做的形变
// 不用make方法,它是相对上一次做的形变
// 还原到初始状态
CGAffineTransformIdentity
比如:平移(其他的旋转rotation、缩放scale都是相同的效果)
// 只平移一次,即使设置多次
self.orgenView.transform = CGAffineTransformMakeTranslation(0, -50);
// 多次设置,会一直往上平移,在上一次的平移的基础上平移
self.orgenView.transform = CGAffineTransformTranslate(self.orgenView.transform, 0, -50);
1) 创建“基于控件初始位置”的形变
CGAffineTransformMakeTranslation(平移)
CGAffineTransformMakeScale(缩放)
CGAffineTransformMakeRotation(旋转)
(2) 创建“基于transform参数”的形变
CGAffineTransformTranslate
CGAffineTransformScale
CGAffineTransformRotate
事件
iOS中的事件可以分为3大类型
image.pngiOS中并不是所有的对象都能处理事件,只有基础于UIResponder的类才行
触摸事件
/* 一根或者多跟手指开始触摸view,系统会自动触发view的下面方法 **/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
/* 一根或者多跟手指在view上移动,系统会自动触发view的下面方法(随着手指的移动,会持续调用该方法) **/
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
/* 一根或者多跟手指离开view,系统会自动触发view的下面方法 **/
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
/* 触摸结束前,某个系统事件(例如电弧呼入)会打断触摸过程,系统会自动低啊用view的下面方法 **/
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
UITouch
* 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象
* 一根手指对应一个UITouch对象
* UITouch的作用:保存着跟手指相关的信息,比如触摸的位置、时间、阶段
* 当手指移动时,系统会更新同一个UITouch对象,使之能一直保存该手指的触摸位置
* 当手指离开屏幕时,系统会销毁相应的UITouch对象
UITouch属性
// 记录了触摸时间产生或者变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;
// 短时间内点击屏幕的次数,可以根据tapCount判断单击、双击或者更多的点击
@property(nonatomic,readonly) NSUInteger tapCount; // touch down within a certain point within a certain amount of time
@property(nonatomic,readonly) UITouchType type API_AVAILABLE(ios(9.0));
@property(nonatomic,readonly) CGFloat majorRadius API_AVAILABLE(ios(8.0));
@property(nonatomic,readonly) CGFloat majorRadiusTolerance API_AVAILABLE(ios(8.0));
// 触摸产生时所处的窗口
@property(nullable,nonatomic,readonly,strong) UIWindow *window;
// 触摸产生时所处的视图view
@property(nullable,nonatomic,readonly,strong) UIView *view;
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers
UITouch方法
// 返回值表示触摸在view上的位置
// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0,0))
// 调用时传入的view参数为nil,返回的是触摸点在UIWindow的位置
- (CGPoint)locationInView:(nullable UIView *)view;
// 记录了前一个触摸点的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;
实例:手指拖动红框
// VC
#import "SeconderViewController.h"
#import "RedView.h"
@interface SeconderViewController ()
@property (nonatomic, strong, readwrite) RedView *redView;
@end
@implementation SeconderViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.redView = [[RedView alloc]initWithFrame:CGRectMake(100, 100, 50, 50)];
self.redView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.redView];
}
@end
// RedView
#import "RedView.h"
@implementation RedView
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 方法原理:通过当前的位置减去上一个位置,计算得出平移量,然后通过transform平移
UITouch *touch = [touches anyObject];
// 求偏移量
CGPoint curPoint = [touch locationInView:self];
CGPoint prePoint = [touch previousLocationInView:self];
CGFloat offsetX = curPoint.x - prePoint.x;
CGFloat offsetY = curPoint.y - prePoint.y;
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
@end
拖动效果.gif
UIEvent
* 每产生一个事件,就会产生一个UIEvent对象
* UIEvent,称为事件对象,记录事件产生的时刻和类型
常见属性
// 事件类型
@property(nonatomic,readonly) UIEventType type API_AVAILABLE(ios(3.0));
@property(nonatomic,readonly) UIEventSubtype subtype API_AVAILABLE(ios(3.0));
// 事件产生的时间
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 事件产生的所有touch
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
// 获取事件在对应的视图上产生的所有touch
- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;
- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
事件的产生和传递
* 发送的触摸事件后,系统就会将事件加入到一个`UIApplication`管理的事件`队列`中
* `UIApplicaiton`会从事件队列中取出`最前面`的事件,并将事件分发下去以便处理,通常先发送事件给应用程序的`主窗口(keyWindow)`
* 主窗口会在视图层次结构中`找到一个最合适的视图来处理触摸事件`,这也是整个事件处理过程的第一步
* 找到合适的视图控件后,就会调用视图控件的`touches方法`来做具体的事件处理
* 触摸事件的传递是`从父控件传递到子控件`
* 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
(UIView不接收触摸事件的三种情况
1>不接受用户交互 userInteractionEnable = NO
2>隐藏 hidden = NO
3>透明 alpha = 0.0 ~ 0.01
)
=====》`如何找到最合适的控件来处理事件`
a>自己是否能接收出发事件
b>触摸点是否在自己身上
c>从后往前遍历子控件,重复前面的两个步骤
d>如果没有符合条件的子控件,那么就自己最合适处理
// 作用:去寻找最合适的View
// 什么时候调用:当一个事件传递给当前的view,就会调用
// 返回值:返回的是谁,谁就是最合适的view(就会调用最合适的view的touch方法)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [super hitTest:point withEvent:event];
}
// 作用:判断当前点在不在它调用View,(谁调用pointInside,这个view就是谁)
// 什么时候调用:它是在hitTest方法当中调用的
// 注意:point点必须要跟它方法调用者在同一个坐标系里面(也就是:point是以当前view的左上角为坐标原点)
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return [super pointInside:point withEvent:event];
}
`hitTest `内部实现大概如下
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.0 判断自己能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.1) {
return nil;
}
// 2. 判断当前点在不在当前view
if (![self pointInside:point withEvent:event]) {
return nil;
}
// 3 从后往前遍历自己的子控件,让子控件重复前两步的操作(把事件传递给,让子控件调用hittest:)
int count = (int)self.subviews.count;
for (int i = count-1; i>=0; i--) {
// 取出每个子控件
UIView *chilView = self.subviews[i];
// 把当前的点转换成子控件坐标系上的点
CGPoint childPoint = [self convertPoint:point toView:chilView];
UIView *fitView = [chilView hitTest:childPoint withEvent:event];
if (fitView) {
return fitView;
}
}
// 4. 没有找到让它自己更合适的view,那么它自己就是最适合的view
return self;
}
========== `事件传递的完整过程` =========
1.先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件
2.调用最适合控件的touches...方法
3.如果调用了[super touches...]就会将事件顺着响应者链条往上传递,传递给上一个响应者
4.接着就会调用上一个响应者的touches...方法
>>>>>>>>>> 如何判断上一个响应者
1.如果当前这个view是控制器的view,那么控制器就是上一个响应者
2.如果当前这个view不是控制器的view,那么父控件就是上一个响应者
网友评论