iOS --- 响应链和事件传递
不知道大家有木有经历过,创建一个按钮在Cell上,但是很多人反馈,点击不了,一点击就走了UITableView的didSelect方法,跳转进去了。
这其实是响应范围只有按钮的位置,UI出来可能就只有这么小点,那我们要怎么做?
可能有小伙伴会说,那就扩大按钮frame啊,多简单的事,立马点击范围就扩大了。
可是。。。如果扩大了,尤其是里面有图片的,一来图片拉伸了,二来可能影响了其他控件的位置。
那能不能变通一下?
那就要了解一下响应链和事件传递了。
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIApplication : UIResponder
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, CALayerDelegate>
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment>
@interface CALayer : NSObject <NSSecureCoding, CAMediaTiming>
我们都知道继承自UIButton的控件是可以点击,这是因为UIApplication,UIView,UIViewController都继承自UIResponder, UIButton->UIControl->UIView, 最终继承自UIView.
备注一下:很多小伙伴都知道的,查找控件的父视图控件方法. nextResponder, 例如:有button类型的按钮btn, btn. nextResponder, btn. nextResponder的父视图btn. nextResponder. nextResponder...以此类推
事件的分发和传递。
1.当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
2.UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
3.UIWindow将事件向下分发,即UIView。
4.UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
5.遍历子控件,重复以上两步。
6.如果没有找到,那么自己就是事件处理者。如果
7.如果自己不能处理,那么不做任何处理。
其中 UIView不接受事件处理的情况主要有以下三种:
1)alpha <0.01
2)userInteractionEnabled = NO
3.hidden = YES.
这个从父控件到子控件寻找处理事件最合适的view的过程,如果父视图不接受事件处理(上面三种情况),则子视图也不能接收事件。事件只要触摸了就会产生,关键在于是否有最合适的view来处理和接收事件,如果遍历到最后都没有最合适的view来接收事件,则该事件被废弃。
怎么寻找最合适的view
// 此方法返回的View是本次点击事件需要的最佳View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
// 判断一个点是否落在范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
// 因为所有的视图类都是继承BaseView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[I];
// 把当前控件上的坐标系转换成子控件上的坐标系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 寻找到最合适的view
return fitView;
}
}
// 循环结束,表示没有比自己更合适的view
return self;
}
事件传递给窗口或控件的后,就调用hitTest:withEvent:方法寻找更合适的view,如果子控件是合适的view,则在子控件再调用hitTest:withEvent:查看子控件是不是合适的view,一直遍历,直到找到最合适的view,或者废弃事件。
有个这样的圆形的button:
这样只要在区域内都绝对可以点击成功的。
image.png那么,如果是这样呢?
这样的点小成这样(只是设极限,现实中会大些,但是也有可能是按钮有图标,实际frame宽高比较小,类似下图)
image.png
1.自定义按钮继承自UIButton
#import "subBtn.h"
@implementation subBtn
// 改变图片的点击范围
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
UIBezierPath *path1 = [UIBezierPath bezierPathWithRect:CGRectMake(-20, -20, 200, 200)];
return [path1 containsPoint:point];
}
@end
2.创建subBtn
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
subBtn *btn = [[subBtn alloc]initWithFrame:CGRectMake(100, 100, 5, 5)];
btn.layer.cornerRadius = btn.frame.size.width / 2;
btn.layer.masksToBounds = YES;
btn.backgroundColor = [UIColor grayColor];
[self.view addSubview:btn];
[btn addTarget:self action:@selector(change) forControlEvents:UIControlEventTouchUpInside];
}
- (void)change{
NSLog(@"please go out");
}
扩大区域后,发现, 圆点外也是可以点击的。点击范围扩大了。