美文网首页
按钮点击控制处理-Runtime

按钮点击控制处理-Runtime

作者: RocketsChen | 来源:发表于2018-07-28 17:18 被阅读340次

    我们设计页面点击的时候肯定会出现多种事件点击的情况,如网络请求验证点击,回调点击,通知点击等等。。。。这样也就可能会造成一种延迟(停顿)的反应,有些页面可以通过加上遮罩防重点击,但有些页面显然不合适,这中情况下控制按钮的点击频率就显得很有必要了。

    举个例子模拟网络回调Push到下个页面,如果我们连续点击会出现什么情况?
    //模拟网络请求延迟(0.5s)
    __weak typeof(self)weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       [weakSelf performSegueWithIdentifier:@"pushToTextVc" sender:self];
    });
    

    连续点击按钮

    GIF演示 栈图

    从_UIModernBarButton - text 我看出栈中加入两层(push两次) HomeVc -> textVc - > textVc

    解决办法:

    • 按钮响应后让屏幕不可点击(HUD)
    • 控制button的action做响应时间限制 (推荐)

    这里我更推荐第二种,以上我只是举出一种很常见的情况,类似这样需要限定重复点击事件的在项目中还是很普遍得,所以第二种方法可以全局处理应用更多场景,而蒙板的使用会有一定限制。

    解决:

    如果说知道利用Runtime去做限制,那首先我们应该考虑怎么样去控制按钮的响应?那得简单介绍一下Target-Action机制。

    • Target-Action机制 : 直译就是“目标-行为”机制,我们给按钮添加点击事件:
    [self.touchClickButton addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    

    按钮的点击事件发生时,会将消息发送到target(此处即为self对象),并由target对象的tapButton:方法来处理相应的事件。但是当sendAction:to:forEvent:addTarget:action:forControlEvents最后处理事件的sendAction。我们从这个方面入手利用Runtime在控制sendAction

    设计:

    从三个方面入手:

    • 创建一个UIButton和分类方法如 - UIButton+DCHook

    • 利用Runtime去交换sendAction:to:forEvent:方法如换成 - dc_sendAction:to:forEvent:

    • 添加一个外部属性可以自己控制按钮间隔的响应时间如 - NSTimeInterval actionInterval

    实现核心代码:

    • UIButton+DCHook.h
    @interface UIButton (DCHook)
    
    #pragma mark - 响应时间的控制
    @property (assign, nonatomic) NSTimeInterval actionInterval;
    
    @end
    
    • UIButton+DCHook.m
    #import <objc/runtime.h> 
    
    @interface UIButton ()
    
    @property (nonatomic, assign) BOOL ignoreEvent; // 是否忽视点击的事件
    
    @end
    
    #define defaultInterval 1.0  //默认时间间隔
    
    static const char *UIControl_enventActionInterval = "UIControl_enventActionInterval";
    static const char *UIControl_enventIsIgnoreEvent = "UIButton_EnventIsIgnoreEvent";
    
    @implementation UIButton (DCHook)
    
    #pragma mark - runtime 动态绑定 属性
    - (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent
    {
        objc_setAssociatedObject(self, UIControl_enventIsIgnoreEvent, @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (BOOL)isIgnoreEvent{
        
        return [objc_getAssociatedObject(self, UIControl_enventIsIgnoreEvent) boolValue];
    }
    
    - (void)setActionInterval:(NSTimeInterval)actionInterval
    {
        objc_setAssociatedObject(self, UIControl_enventActionInterval, @(actionInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSTimeInterval)actionInterval
    {
        return [objc_getAssociatedObject(self, UIControl_enventActionInterval) doubleValue];
    }
    
    #pragma mark - Load
    + (void)load
    {
        SEL selA = @selector(sendAction:to:forEvent:);
        SEL selB = @selector(dc_sendAction:to:forEvent:);
        Method methodA = class_getInstanceMethod([UIButton class],selA);
        Method methodB = class_getInstanceMethod([self class], selB);
        
        objc_setAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        BOOL isAdd = class_addMethod([UIButton class], selA, method_getImplementation(methodB), method_getTypeEncoding(methodB));
        
        if (isAdd) {
            class_replaceMethod([UIButton class], selB, method_getImplementation(methodA), method_getTypeEncoding(methodA));
        }else{
            //添加失败了 说明本类中有methodB的实现,此时只需要将methodA和methodB的IMP互换一下即可。
            method_exchangeImplementations(methodA, methodB);
        }
    }
    
    #pragma mark - 交换方法
    - (void)dc_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
    {
        NSTimeInterval interval = [objc_getAssociatedObject(self, UIControl_enventActionInterval) doubleValue];
        if ([objc_getAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent) boolValue]){ //忽略直接返回
            return;
        }else if (defaultInterval > 0){ // 点击事件
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(((interval == 0) ? defaultInterval : interval)  * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                objc_setAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            });
        }
        
        objc_setAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent, @(YES), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        // 这里看上去会陷入递归调用死循环,但在运行期此dc_sendAction方法是和sendAction:to:forEvent:互换的,相当于执行sendAction:to:forEvent:方法,所以并不会陷入死循环。
        [self dc_sendAction:action to:target forEvent:event];
    }
    
    @end
    

    设置默认时间间隔为1s,可手动通过actionInterval更改如下

    self.touchClickButton.actionInterval = 3; //测试更改为3s
    
    打印响应图

    这样就算你再怎么连续点击也不过造成重复响应的问题了

    这里有在自定义中一个细节
    #define defaultInterval 1.0  //默认时间间隔
    
    NSTimeInterval interval = [objc_getAssociatedObject(self, UIControl_enventActionInterval) doubleValue];
    (interval == 0) ? defaultInterval : interval;
    

    我通过三目判断未设置属性时默认为1s间隔,那如果出现就需要重复点击的页面我们只需要把actionInterval设置为CGFLOAT_MIN即可;

    // CGFLOAT_MIN : Float类型的最小非负值非低于正常值
    self.touchClickButton.actionInterval = CGFLOAT_MIN;
    

    如果说在不需要间隔的情况下每次都要设置属性觉得麻烦,也可以创建一个DCNOIntervalButton父类,以后继承就好了。

    demo地址:DailyDemo

    相关文章

      网友评论

          本文标题:按钮点击控制处理-Runtime

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