手势 & 响应链

1 事件产生与传递

目的:找到可能的处理事件的Responder。传递顺序,依赖于视图树,从树根到树叶。

1.1 基于UIResponder的hitTest & pointInside

事件传递参考这2篇文章,一篇搞定事件传递、响应者链条、hitTest和pointInside的使用
iOS 事件传递与响应链

简单概括下,

  1. hitTest是看自己+subviews能不能响应。

2. pointInside是hitTest的子过程。

3. 满足(1)-(4),才看自己的subviews能不能响应。

以下六种情况中,hitTest返回nil。
(1)[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
(2)不接收用户交互 userInteractionEnabled = NO
(3)隐藏 hidden = YES
(4)透明 alpha = 0.0 ~ 0.01
(5)pointInside返回No
(6)自己的所有subview的hitTest也返回nil
subview的访问顺序,是从数组末尾往前找,也就是从离用户最近的view开始。

2 响应链,由小到大

上一节中,已经找到了最合适的UIResponder,从他开始,通过nextResponder,找出最终处理事件的UIResponder。

一个例子就是TableView能响应滚动(PanGesture),cell上有一些元素能响应click(如新闻收藏、点赞、打开)。如果确实是Pan,虽然PanGesutre的Responder是在下方,但仍然是最终处理的事件仍然是Pan,Responder是TableView。

2.1 先手势,再UIControl,最后UIResponder

手势的响应继承于UIControl的控件继承于UIScroll的控件在识别出用户操作后,都会结束响应者链,不再往上传递

  1. UIControl可以处理的事件,如:UIButton的UITouchUpInside等,这些事件实际上是对UITouchBegan的封装,所以优先级高于UITouchBegan

2. UIResponder的时间,如:UIView的UITouchBegan等

image.png

image.png

3 一些应用

3.1 键盘消失

1. self.view上加Tap手势

2. scrollView的手动上下滑

3. viewWillDisappear

4. 其他特定需求

3.2 Panel消失

panel处于一个透明(半透明)view上,这个view上加点击事件。

3.3 UIView添加Tap后,防止UITableViewCell上的点击失效



- (void)viewDidLoad {
    //...
    UITapGestureRecognizer  *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
    gesture.delegate = self;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([touch.view isKindOfClass:[UITableView class]]) {
        return NO;
    }
    if ([NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"]) {
        return NO;
    }
    return YES;
}

3.4 扩大button响应范围

与pointInside、hittest有关

3.5 没有clip时,点在超出的subview上也不会响应

[图片上传失败...(image-bbb9a-1560185032590)]

原理就是事件传递的方式。

同理,父视图userInteractionEnabled设置为No后,子视图就收不到事件了。

3.6 防止短时间内连续点击

原理:hook UIControl的sendAction方法。做一个短时失效的处理。

#import "UIControl+Limit.h"
#import 
static const char *UIControl_acceptEventInterval="UIControl_acceptEventInterval";
static const char *UIControl_ignoreEvent="UIControl_ignoreEvent";

@implementation UIControl (Limit)

#pragma mark - acceptEventInterval
- (void)setAcceptEventInterval:(NSTimeInterval)acceptEventInterval {
    objc_setAssociatedObject(self,UIControl_acceptEventInterval, @(acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSTimeInterval)acceptEventInterval {
    return [objc_getAssociatedObject(self,UIControl_acceptEventInterval) doubleValue];
}

#pragma mark - ignoreEvent
-(void)setIgnoreEvent:(BOOL)ignoreEvent {
    objc_setAssociatedObject(self,UIControl_ignoreEvent, @(ignoreEvent), OBJC_ASSOCIATION_ASSIGN);
}

-(BOOL)ignoreEvent {
    return [objc_getAssociatedObject(self,UIControl_ignoreEvent) boolValue];
}

#pragma mark - Swizzling
+(void)load {
    Method a = class_getInstanceMethod(self,@selector(sendAction:to:forEvent:));
    Method b = class_getInstanceMethod(self,@selector(swizzled_sendAction:to:forEvent:));
    method_exchangeImplementations(a, b);//交换方法
}
- (void)swizzled_sendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event {
    if(self.ignoreEvent){
        NSLog(@"btnAction is intercepted");
        return;
}

    if(self.acceptEventInterval>0) {
        self.ignoreEvent=YES;
        [self performSelector:@selector(setIgnoreEventWithNo) withObject:nil afterDelay:self.acceptEventInterval];
    }
    [self swizzled_sendAction:action to:target forEvent:event];
}

-(void)setIgnoreEventWithNo {
    self.ignoreEvent=NO;
}
@end

3.7 自定义导航栏后,左滑失效,解决手势冲突

只能说和navigationController.navigationBar.hidden=YES;有关

@property (nonatomic, weak)id originalNavigateDelegate;
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.originalNavigateDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
    self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    self.navigationController.interactivePopGestureRecognizer.delegate = self.originalNavigateDelegate;
}

3.8 处理视频进度条、音量;微信语音输入

//PanGestureRecognizer处理视频进度条、音量
//根据state分别处理
//1. Begin和第一次达到阈值的变化,区分滑动类型
//2. 离开前,更新进度条、时间、缩略图
//3. 离开时,更新视频进度

//PanGestureRecognizer处理微信语音输入
//1. 根据位置,更新提示(取消发送、发送)
//2. 根据音量,更新音量条
//3. 同步更新语音识别文字
//4. 离开时,发送

你可能感兴趣的:(手势 & 响应链)