按钮点击控制处理-Runtime

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

举个例子模拟网络回调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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容