内容简介:在iOS中的view之间逐层叠加,当点击了屏幕上的某个view时,这个点击动作会由硬件层传导到操作系统并生成一个事件(Event),这个事件顺着view的层级由下往上传导,直至找到包含有这个点击点、层级最高、且可与用户交互的view来响应这个事件。事件的传递过程官网有图解:以下代码是在A视图中都重写我们需要观察的几个父类方法,BCDE中需要重写的代码以此类推:当点击了一下B视图所在区域时,Xcode输出log如下:
在iOS中的view之间逐层叠加,当点击了屏幕上的某个view时,这个点击动作会由硬件层传导到操作系统并生成一个事件(Event),这个事件顺着view的层级由下往上传导,直至找到包含有这个点击点、层级最高、且可与用户交互的view来响应这个事件。事件的传递过程官网有图解:
2. 响应链中涉及的方法
- UIView中的hitTest方法、pointInside方法
// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system - (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; - (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view; - (CGPoint)convertPoint:(CGPoint)point fromView:(nullable UIView *)view; - (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view; - (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view; 复制代码
- 点击事件会在hitTest、pointInside两个方法配合的情形下,向下传递;
- hitTest:withEvent:在内部首先会判断该视图是否能响应触摸事件,如果不能响应,返回nil,表示该视图不响应此触摸事件。然后再调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内)。如果pointInside:withEvent:返回NO,那么hiteTest:withEvent:也直接返回nil;
- 如果pointInside:withEvent:返回YES,则向当前视图的所有子视图发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历。直到有子视图返回非空对象或者全部子视图遍历完毕;若第一次有子视图返回非空对象,则 hitTest:withEvent:方法返回此对象,处理结束;如所有子视图都返回非,则hitTest:withEvent:方法返回该视图自身。
- UIResponder中的touchesBegan、touchesMoved、touchesEnded等方法
// Generally, all responders which do custom touch handling should override all four of these methods. // Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each // touch it is handling (those touches it received in touchesBegan:withEvent:). // *** You must handle cancelled touches to ensure correct behavior in your application. Failure to // do so is very likely to lead to incorrect behavior or crashes. - (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); // 这几个方法比较常用,在此不再敖述; // 当然,UIResponder中不止这三个响应事件的方法,本文仅以touches的这三个方法为例。 复制代码
- 示例 为了使我们更好的理解事件响应过程中,上述UIView与UIResponder这几个方法的执行过程,我们用以下图示例(示例参考文章)进行说明,图中视图ABCDE(UIView型)之间的层次关系是self.view(A(B, C(D, E))):
以下代码是在A视图中都重写我们需要观察的几个父类方法,BCDE中需要重写的代码以此类推:
/* * 例如:A中重写父类方法的代码, */ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { NSLog(@"AView ---->> hitTest:withEvent: ---"); UIView * view = [super hitTest:point withEvent:event]; NSLog(@"AView <<--- hitTest:withEvent: --- /n hitTestView:%@", view); return view; } - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event { NSLog(@"AView --->> pointInside:withEvent: ---"); BOOL isInside = [super pointInside:point withEvent:event]; NSLog(@"AView <<--- pointInside:withEvent: --- isInside:%d", isInside); return isInside; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"AView touchesBegan"); } - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { NSLog(@"AView touchesMoved"); } - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { NSLog(@"AView touchesEnded"); } 复制代码
当点击了一下B视图所在区域时,Xcode输出log如下:
在示例中可以发现响应链中所涉及方法的执行过程,有以下特点
- 当UIView中的isUserInteractionEnabled = NO、isHidden = YES、alpha <= 0.01时,hitTest方法不会被调用;
- UIResponder 中的touches三个方法都是发生在找到最终的响应事件的view之后;
- 二是寻找hit-test view的事件链传导了两遍,具体原因不明;
3. hitTest方法的应用
-
改变UIButton的响应热区具体的说改变视图的响应热区,主要是在pointInside方法中完成的,QiShare关于改变热区的文章中有过描述。但是hitTest、pointInside同属响应链中方法,如果有需求,也可以在hitTest中返回一个***确定的view***。
-
view超出superView的bounds仍能响应事件如图,在黄色superView上添加一个UIButton,UIButton上半部分超出superView。正常的情况下点击红框区域时,UIButton是无法响应点击事件的,要让红框区域内的UIButton仍能响应点击事件,需要我们重写superView的hitTest方法。
#import "BeyondBoundsOfView.h" @interface BeyondBoundsOfView () @property (nonatomic, strong) UIButton *button; @end @implementation BeyondBoundsOfView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _button = [UIButton buttonWithType:UIButtonTypeSystem]; [_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [_button setTitle:@"UIButton" forState:UIControlStateNormal]; [_button setBackgroundColor:[UIColor lightGrayColor]]; _button.frame = CGRectMake(0, 0, 80, 80); [self addSubview:_button]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; CGSize size = self.frame.size; _button.center = CGPointMake(size.width / 2, 0); } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) { return nil; } for (UIView *subview in self.subviews) { CGPoint convertedPoint = [subview convertPoint:point fromView:self]; UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event]; if (hitTestView) { return hitTestView; } } return nil; } @end 复制代码
上面代码中关键的一行: CGPoint convertedPoint = [subview convertPoint:point fromView:self]; 获取到convertedPoint对我们循环调用子view的hitTest很关键。
小编微信:可加并拉入《QiShare技术交流群》。
关注我们的途径有:
QiShare(微信公众号)
以上所述就是小编给大家介绍的《UIView 中的hitTest方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 理解实例方法、类方法、静态方法
- 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(上篇)
- 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(上篇)
- java:方法覆盖与方法重载
- 静态方法、实例化方法与线程安全
- JS数组方法总览及遍历方法耗时统计
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
浪潮之巅(上册)
吴军 / 人民邮电出版社 / 2013-5-1 / 35.00元
《浪潮之巅(第2版)(上册)》不是一本科技产业发展历史集,而是在这个数字时代,一本IT人非读不可,而非IT人也应该阅读的作品。一个企业的发展与崛起,绝非只是空有领导强人即可达成。任何的决策、同期的商业环境,都在都影响着企业的兴衰。《浪潮之巅》不只是一本历史书,除了讲述科技顶尖企业的发展规律,对于华尔街如何左右科技公司,以及金融风暴对科技产业的冲击,也多有着墨。此外,《浪潮之巅》也着力讲述很多尚在普......一起来看看 《浪潮之巅(上册)》 这本书的介绍吧!
Base64 编码/解码
Base64 编码/解码
SHA 加密
SHA 加密工具