#import "XMGWindow.h"
/**
1:注意点:hitTest方法内部会调用pointInside方法,询问触摸点是否在自己身上,当遍历子控件时,传入的坐标点要进行转化,将父视图上的坐标点转换到所要传递的子视图上的坐标点
2:hitTest的底层实现:当控件接收到触摸事件的时候,不管能不能处理事件,都会调用hitTest方法,此方法的底层实现是:1:先看自己是否能接受触摸事件 2:再看触摸点是否在自己身上 3:从后往前遍历子控件,拿到子控件后,再次重复1,2步骤,要把父控件上的坐标点转换为子控件坐标系下的点,再次执行hitTest方法。 3:若是最后还没有找到合适的view,那么就return self,自己就是合适的view */
@implementation XMGWindow //作用:去寻找最适合的View
//什么时候调用:当一个事件传递给当前View,就会调用.
//返回值:返回的是谁,谁就是最适合的View(就会调用最适合的View的touch方法)
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ //1.判断自己能否接收事件
if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}
//2.判断当前点在不在当前View.
if (![self pointInside:point withEvent:event]) {
return nil;
}
//3.从后往前遍历自己的子控件.让子控件重复前两步操作,(把事件传递给,让子控件调用hitTest)
int count = (int)self.subviews.count;
for (int i = count - ; i >= ; i--) {
//取出每一个子控件
UIView *chileV = self.subviews[i];
//把当前的点转换成子控件坐标系上的点.
CGPoint childP = [self convertPoint:point toView:chileV];
UIView *fitView = [chileV hitTest:childP withEvent:event];
//判断有没有找到最适合的View
if(fitView){
return fitView;
}
} //4.没有找到比它自己更适合的View.那么它自己就是最适合的View
return self; } //作用:判断当前点在不在它调用View,(谁调用pointInside,这个View就是谁)
//什么时候调用:它是在hitTest方法当中调用的.
//注意:point点必须得要跟它方法调用者在同一个坐标系里面
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
return YES;
} @end

2:hitTest 方法的练习1:

业务逻辑:

底部一个按钮, 按钮的上面有一个View,遮挡在按钮的上面.

点击View时, View接收事件,当发现点击的点在按钮的位置时, 让底部的按钮处理事件.

实现思路:

实现View的touchBegain方法,先坚听UIView的点击.

并去实现UIView的HitTest方法, 在hitTest方法当中通过把当前点转换成按钮所在的坐标系

CGPoint btnP = [self convertPoint:point toView:self.btn];

转换过后查看当前点在不在按钮上,如果在按钮上,就直接返回按钮.

如果有在按钮上,保持系统默认做法.

实现代码:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

判断当前点在不在按钮上.

把当前点转换成按钮所在的坐标系

CGPoint btnP = [self convertPoint:point toView:self.btn];

if ([self.btn pointInside:btnP withEvent:event]) {

return self.btn;

}else{

return [super hitTest:point withEvent:event];

}

}

#import "BlueView.h"

@interface BlueView()

@property(nonatomic, weak) IBOutlet UIButton *btn;

@end

@implementation BlueView

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

    //NSLog(@"%@",self.btn);
//return [super hitTest:point withEvent:event];
//拿到后面的按钮
//当点在按钮上的时候,才返回按钮,如果不在按钮上.保持系统默认做法 //判断点在不在按钮身上
//把当前的点转换到按钮身上的坐标系的点
CGPoint btnP = [self convertPoint:point toView:self.btn];
if ([self.btn pointInside:btnP withEvent:event]) {
return self.btn;
}else{
return [super hitTest:point withEvent:event];
} } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
} @end

注意:在storyBoard中定义了view和按钮,view自定义可以拖线到自定义的view中,前提是得进行类的关联,但是button不能拖入,因为button不属于view的子控件,解决:可以在view中属性定义IBOutlet,在拖线到storyBoard中的按钮,这样就在view中拿到了不属于view的button

2:hitTest练习2

业务逻辑:
按钮可以随着⼿手指拖动⽽而拖动.拖动过程当中,按钮当中的⼦子控件也跟着拖动.
让超过按钮的⼦子控件也能够响应事件,⼀一般情况下,当⼀一个控件超过他的⽗父控件的时候,是不能
够接收事件的.
现在要做的事情就让超过⽗父控件的按钮也能够响应事件.

#import "ViewController.h"
#import "chatBtn.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)btnClick:(chatBtn *)sender { UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setImage:[UIImage imageNamed:@"对话框"] forState:UIControlStateNormal];
[btn setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted]; sender.popBtn = btn; btn.frame = CGRectMake(, -, , );
[sender addSubview:btn];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end
#import <UIKit/UIKit.h>

@interface chatBtn : UIButton

/** 弹出的按钮 */
@property (nonatomic, weak)UIButton *popBtn; @end
#import "chatBtn.h"

@implementation chatBtn

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.popBtn) {
//return self.popBtn;
//判断当前点在不在popBtn身上
//把当前点转换popBtn身上的点
CGPoint popBtnP = [self convertPoint:point toView:self.popBtn];
if ( [self.popBtn pointInside:popBtnP withEvent:event]) {
return self.popBtn;
}else{
return [super hitTest:point withEvent:event];
} }else{
return [super hitTest:point withEvent:event];
} } -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //1.获取UITouch
UITouch *touch = [touches anyObject];
//2.获取当前手指的点,上一个手指的点
CGPoint curP = [touch locationInView:self];
CGPoint preP = [touch previousLocationInView:self];
//3.计算偏移量
CGFloat offsetX = curP.x - preP.x;
CGFloat offsetY = curP.y - preP.y;
//4.平移
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY); } @end

一般情况下,当⼀一个控件超过他的⽗父控件的时候,是不能 够接收事件的.原因是:1:当产生触摸事件后,系统会将触摸事件发送到由UIApplication管理的事件队列中,UIApplication会将队列中最前端的事件取出来交给keywindow去处理,主窗口keywindow会1:查看自身能不能接受触摸事件 2:触摸点是不是在自身上  3:若是前两个条件都满足则其会遍历自身的子控件,且是从后到前遍历,也就是从子控件数组的最后一个控件开始遍历,在执行前两个步骤,若是不再则遍历下一个子控件,若是一直没找到,则自己就是最合适处理事件的view,若在,则继续重复前两个步骤,直到找到最合适的view。2:当点击按钮的时候window将事件传递到白色view,白色view从后往前遍历子控件,先遍历蓝色的button,在遍历点击对话框找到适合处理事件的view。当子控件超出父控件的范围后,点击子控件时不会响应事件,原因是:当遍历到父控件点击对话框时,触摸点不再其身上,则其就不会响应事件

实现思路:
第⼀一步,先办到让按钮能够跟随着⼿手指移动⽽而移动. 实现按钮的touchesMoved⽅方法,在touchesMoved⽅方法当中,获得当前⼿手指所在的点.以前上⼀一 个点.一个手指对应一个UITouch对象,一个手指取出UITouch,anyobject,若是多根手指则会touch.allobjects获取所有的UITouch对象
分别计算X轴的偏移量以及Y轴的偏移量. 然后修改当前按钮的transform让按钮办到能够跟随着⼿手指移动⽽而移动(累加形变).

第⼆二步,1: 实现按钮的hitTest⽅方法. 在该⽅方法当中去判断当前的点在不在按钮的⼦子控件上. 如果在按钮的⼦子控件上.就返回按钮的⼦子控件如果不在的话, 就保持系统的默认做法.一般在父类中重写hitTest方法,修改返回最合适的view 2:self.chatBtn.btn定义为弱引用, self.chatBtn.btn = btn赋值指针地址,弱引用指向该对象,[self.chatBtn addSubview:btn];强引用引用着btn使其不被销毁,所以可以用weak 3:事件会由白色view传递到其子控件self.chatBtn上,所以在self.chatBtn的类里重写hitTest方法,修改返回的view

实现代码: 1.实现点击添加⼦子控制器

   - (IBAction)chatBtnClick:(id)sender {

UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; [btn setImage:[UIImage imageNamed:@"对话框"]

forState:UIControlStateNormal];
[btn setImage:[UIImage imageNamed:@"⼩小孩"]

forState:UIControlStateHighlighted];
btn.frame = CGRectMake(self.chatBtn.bounds.size.width * 0.5,

-80, 100, 80); self.chatBtn.btn = btn;

[self.chatBtn addSubview:btn]; }

2.⾃自定义按钮,实现拖动按钮

让按钮跟着⼿手指移动⽽而移动.
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

获取当前⼿手指对象

UITouch *touch = [touches anyObject]; 获取当前⼿手指所在的点

CGPoint curP = [touch locationInView:self]; 获取上⼀一个⼿手指所在的点

CGPoint preP = [touch previousLocationInView:self]; X轴的偏移量

CGFloat offsetX = curP.x - preP.x; Y轴的偏移量
CGFloat offsetY = curP.y - preP.y; 移动

    self.transform = CGAffineTransformTranslate(self.transform,
offsetX, offsetY);

}

3.拦截hitTest⽅方法
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

判断当前的点在不在上⾯面的按钮上.

  先把点转换成上⾯面按钮上⾯面的点

CGPoint btnP = [self convertPoint:point toView:self.btn]; 判断点在不在按钮上.

if ([self.btn pointInside:btnP withEvent:event]) { 让按钮点击响应事件

        return self.btn;
}else{

保持系统默认做法

        return [super hitTest:point withEvent:event];
}

}

ios开发事件处理之 四:hittest方法的底层实现与应用的更多相关文章

  1. iOS开发 GET、POST请求方法(NSURLSession篇)

    NSURLConnection,在iOS9被宣布弃用,本文不使用NSURLConnection进行网络编程,有兴趣的童鞋可以参考: [iOS开发 GET.POST请求方法(NSURLConnectio ...

  2. iOS开发 GET、POST请求方法:NSURLSession篇

    NSURLConnection,在iOS 9被宣布弃用,本文不使用NSURLConnection进行网络编程,有兴趣的童鞋可以参考: iOS开发 GET.POST请求方法(NSURLConnectio ...

  3. ipa包如何打包?ios打包ipa的四种方法分享

      今天带来的内容是ios打包ipa的四种方法.总结一下,目前.app包转为.ipa包的方法有以下几种,下面一起来看看吧!    1.Apple推荐的方式,即实用xcode的archive功能 Xco ...

  4. iOS开发 GET、POST请求方法(NSURLConnection篇)

    Web Service使用的主要协议是HTTP协议,即超文本传输协议. HTTP/1.1协议共定义了8种请求方法(OPTIONS.HEAD.GET.POST.PUT.DELETE.TRACE.CONN ...

  5. iOS开发中的Html解析方法

    iOS开发中的Html解析方法 本文作者为大家介绍了在iOS开发中的Html解析方法,并同时提供了Demo代码的下载链接,Demo 解析了某个网站(具体可在代码中查看)的html网页,提取了图片以及标 ...

  6. iOS开发-关闭/收起键盘方法总结

    前言:作为IOS开发人员,需要经常和表单打交道.因此我对收起键盘的方法作了下总结,IOS收起键盘有三种方法(如果有其它收起键盘的方法请在留言区指错). 收起键盘的方法: 1.点击Return按扭时收起 ...

  7. 【转】iOS开发-关闭/收起键盘方法总结

    原文网址:http://www.cnblogs.com/GarveyCalvin/p/4167759.html 前言:作为IOS开发人员,需要经常和表单打交道.因此我对收起键盘的方法作了下总结,IOS ...

  8. iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题

    一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property ( ...

  9. 【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态

    一. Objective-C 方法详解 1. 方法属性 (1) OC 方法传参机制 Object-C 方法传参机制 : OC 中得参数传递都是值传递, 传入参数的是参数的副本; -- 基本类型 (值传 ...

随机推荐

  1. golang标准包中文手册

    golang标准包中文手册 http://files.cnblogs.com/files/rojas/liudiwu-pkgdoc-master.zip

  2. natapp解决Invalid Host header的问题

    最近在做一个微信公众号项目,用微信开发工具调试本地项目,需要做一下内网穿透,代理都配置好了,页面出现这个Invalid Host header错误,内网穿透工具我是用的frps做的,最后通过googl ...

  3. BZOJ3626: [LNOI2014]LCA(树链剖分+线段树)

    Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q ...

  4. 洛谷 P2616 [USACO10JAN]购买饲料II Buying Feed, II

    洛谷 P2616 [USACO10JAN]购买饲料II Buying Feed, II https://www.luogu.org/problemnew/show/P2616 题目描述 Farmer ...

  5. Activity启动过程源代码分析

    事实上写分析源代码文章总会显得非常复杂非常乏味,可是梳理自己看源代码时的一些总结也是一种提高. 这篇博客分析下Activity启动过程源代码,我会尽量说得简单点. 个人的观点是看源代码不能看得太细,否 ...

  6. JavaScript---call()使用的一些疑问

    疑问:在使用.call()时,调用对象到底是否可以直接拥有了被调用者的方法和属性? 这里输出结果为:ReferenceError: o is not defined function Person(n ...

  7. amazeui学习笔记--css(常用组件13)--进度条Progress

    amazeui学习笔记--css(常用组件13)--进度条Progress 一.总结 1.进度条基本使用:进度条组件,.am-progress 为容器,.am-progress-bar 为进度显示信息 ...

  8. mysql 表的timestamp为自动添加

    新设计表时可以执行语句: `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP C ...

  9. StackExchange.Redis 官方文档(五) Keys, Values and Channels

    原文:StackExchange.Redis 官方文档(五) Keys, Values and Channels Keys, Values and Channels 在使用redis的过程中,要注意到 ...

  10. 8.6 Android灯光系统_源码分析_背光灯

    Change system screen brightness, using android.provider.Settings.System.SCREEN_BRIGHTNESSandroid-er. ...