美文网首页iOS
手势 & 响应链

手势 & 响应链

作者: 景天儿 | 来源:发表于2019-06-11 00:52 被阅读0次

    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上的点击失效

    <UIGestureRecognizerDelegate>
    
    - (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 <objc/runtime.h>
    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. 离开时,发送
    

    相关文章

      网友评论

        本文标题:手势 & 响应链

        本文链接:https://www.haomeiwen.com/subject/zjnxiqtx.html