目录

  • 使用场景
  • 方法一 设置enableduserInteractionEnabled属性
  • 方法二 借助cancelPreviousPerformRequestsWithTarget:selector:object实现
  • 方法三 通过runtime交换方法实现
  • 注意事项

一 使用场景

在实际应用场景中,有几个业务场景需要控制UIButton响应事件的时间间隔。

  • 1 当点击按钮来执行网络请求时,若请求耗时稍长,用户往往会多次点击。这样,就执行了多次请求,造成资源浪费。
  • 2 在移动终端设备性能较差时,连续点击按钮会执行多次事件(比如push出来多个viewController)。
  • 3 防止暴力点击。

二 方法一

通过UIButtonenabled属性和userInteractionEnabled属性控制按钮是否可点击。此方案在逻辑上比较清晰、易懂,但具体代码书写分散,常常涉及多个地方。

  • 创建按钮
- (void)drawBtn {UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 100)];[btn setTitle:@"按钮点击" forState:UIControlStateNormal];[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];// 按钮不可点击时,文字颜色置灰[btn setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];[btn setTitleColor:[UIColor blueColor] forState:UIControlStateHighlighted];btn.center = self.view.center;[btn addTarget:self action:@selector(tapBtn:) forControlEvents:UIControlEventTouchUpInside];[self.view addSubview:btn];
}

按钮不可点击时,标题颜色置灰,方便对比

  • 点击事件
- (void)tapBtn:(UIButton *)btn {NSLog(@"按钮点击...");btn.enabled = NO;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{btn.enabled = YES;});
}

运行结果

1.gif

2019-06-12 23:21:09.039455+0800 AvoidBtnRepeatClick[8102:362250] 按钮点击...
2019-06-12 23:21:11.658751+0800 AvoidBtnRepeatClick[8102:362250] 按钮点击...
2019-06-12 23:21:14.057510+0800 AvoidBtnRepeatClick[8102:362250] 按钮点击...
2019-06-12 23:21:16.254230+0800 AvoidBtnRepeatClick[8102:362250] 按钮点击...
2019-06-12 23:21:18.788004+0800 AvoidBtnRepeatClick[8102:362250] 按钮点击...
2019-06-12 23:21:21.155584+0800 AvoidBtnRepeatClick[8102:362250] 按钮点击...
2019-06-12 23:21:23.389769+0800 AvoidBtnRepeatClick[8102:362250] 按钮点击...

每隔2秒执行一次方法

方法二

通过 NSObject 的两个方法

// 此方法会在连续点击按钮时取消之前的点击事件,从而只执行最后一次点击事件
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
// 多长时间后做某件事情
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

按钮创建还是上面代码

  • 按钮点击事件如下
/** 方法一 */
- (void)tapBtn:(UIButton *)btn {NSLog(@"按钮点击了...");// 此方法会在连续点击按钮时取消之前的点击事件,从而只执行最后一次点击事件[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClickedAction:) object:btn];// 多长时间后做某件事情[self performSelector:@selector(buttonClickedAction:) withObject:btn afterDelay:2.0];
}- (void)buttonClickedAction:(UIButton *)btn {NSLog(@"真正开始执行业务 - 比如网络请求...");
}
  • 运行结果

1.gif

2019-06-13 09:15:58.935540+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:15:59.284096+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:15:59.760772+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:00.238923+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:00.689305+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:02.689633+0800 AvoidBtnRepeatClick[62321:2927724] 真正开始执行业务 - 比如网络请求...
2019-06-13 09:16:03.479984+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:03.884124+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:04.334930+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:04.776324+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:05.179153+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:07.179512+0800 AvoidBtnRepeatClick[62321:2927724] 真正开始执行业务 - 比如网络请求...
2019-06-13 09:16:08.062850+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:10.064171+0800 AvoidBtnRepeatClick[62321:2927724] 真正开始执行业务 - 比如网络请求...
2019-06-13 09:16:10.947205+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:12.948065+0800 AvoidBtnRepeatClick[62321:2927724] 真正开始执行业务 - 比如网络请求...
2019-06-13 09:16:13.528897+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:13.776711+0800 AvoidBtnRepeatClick[62321:2927724] 按钮点击了...
2019-06-13 09:16:15.777735+0800 AvoidBtnRepeatClick[62321:2927724] 真正开始执行业务 - 比如网络请求...

通过打印结果可知,如果连续点击多次,只会响应最后一次点击事件,并且是在设定的时间间隔后执行,这边设置的时间间隔是 2S。

总结:会出现延时现象,并且需要对大量的UIButton做处理,工作量大,不方便。

方法三

通过Runtime交换UIButton的响应事件方法,从而控制响应事件的时间间隔。

实现步骤如下:

  • 1 创建一个UIButton的分类,使用runtime增加public属性cs_eventIntervalprivate属性cs_eventInvalid
  • 2 在+load方法中使用runtimeUIButton-sendAction:to:forEvent:方法与自定义的cs_sendAction:to:forEvent:方法进行交换
  • 3 使用cs_eventInterval作为控制cs_eventInvalid的计时因子,用cs_eventInvalid控制UIButtonevent事件是否有效。

*代码实现如下

@interface UIButton (Extension)/** 时间间隔 */
@property(nonatomic, assign)NSTimeInterval cs_eventInterval;@end
#import "UIButton+Extension.h"
#import <objc/runtime.h>static char *const kEventIntervalKey = "kEventIntervalKey"; // 时间间隔
static char *const kEventInvalidKey = "kEventInvalidKey";   // 是否失效@interface UIButton()/** 是否失效 - 即不可以点击 */
@property(nonatomic, assign)BOOL cs_eventInvalid;@end@implementation UIButton (Extension)+ (void)load {// 交换方法Method clickMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));Method cs_clickMethod = class_getInstanceMethod(self, @selector(cs_sendAction:to:forEvent:));method_exchangeImplementations(clickMethod, cs_clickMethod);
}#pragma mark - click- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {if (!self.cs_eventInvalid) {self.cs_eventInvalid = YES;[self cs_sendAction:action to:target forEvent:event];[self performSelector:@selector(setCs_eventInvalid:) withObject:@(NO) afterDelay:self.cs_eventInterval];}
}#pragma mark - set | get- (NSTimeInterval)cs_eventInterval {return [objc_getAssociatedObject(self, kEventIntervalKey) doubleValue];
}- (void)setCs_eventInterval:(NSTimeInterval)cs_eventInterval {objc_setAssociatedObject(self, kEventIntervalKey, @(cs_eventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (BOOL)cs_eventInvalid {return [objc_getAssociatedObject(self, kEventInvalidKey) boolValue];
}- (void)setCs_eventInvalid:(BOOL)cs_eventInvalid {objc_setAssociatedObject(self, kEventInvalidKey, @(cs_eventInvalid), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
  • 测试代码如下
/** 方法三 */
- (void)drawExpecialBtn{UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 100)];[btn setTitle:@"按钮点击" forState:UIControlStateNormal];[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];// 按钮不可点击时,文字颜色置灰[btn setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];[btn setTitleColor:[UIColor blueColor] forState:UIControlStateHighlighted];btn.center = self.view.center;[btn addTarget:self action:@selector(tapBtn:) forControlEvents:UIControlEventTouchUpInside];btn.cs_eventInterval = 2.0;[self.view addSubview:btn];
}- (void)tapBtn:(UIButton *)btn {NSLog(@"按钮点击...");
}
  • 运行结果如下

1.gif

2019-06-13 19:18:48.314110+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:18:50.346907+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:18:52.512887+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:18:54.515119+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:18:56.577693+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:18:58.679121+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:19:00.681003+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:19:02.752387+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...
2019-06-13 19:19:04.879559+0800 AvoidBtnRepeatClick[89795:3312038] 按钮点击...

四 注意事项

在方法三中交互UIButtonsendAction:to:forEvent:方法,实际上交互的是UIControlsendAction:to:forEvent:方法,所以在使用·UIControl·或其·子类(比如UISlider)·的·sendAction:to:forEvent:·方法时会引起参数缺失的崩溃。

  • 测试代码如下
/** 注意事项 */
- (void)slideTest {UISlider *slide = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, 200, 59)];[slide addTarget:self action:@selector(tapSlide:) forControlEvents:UIControlEventTouchUpInside];slide.center = self.view.center;[self.view addSubview:slide];
}- (void)tapSlide:(UISlider *)slider {NSLog(@"UISlider点击...");
}

运行结果

image.png

(void *) $0 = 0x0000600002620000
2019-06-13 19:48:22.753320+0800 AvoidBtnRepeatClick[90086:3328087]  INFO: Reveal Server started (Protocol Version 32).
2019-06-13 19:48:26.329630+0800 AvoidBtnRepeatClick[90086:3328087] -[UISlider cs_eventInvalid]: unrecognized selector sent to instance 0x7ffd79c0f4d0
2019-06-13 19:48:26.340542+0800 AvoidBtnRepeatClick[90086:3328087] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UISlider cs_eventInvalid]: unrecognized selector sent to instance 0x7ffd79c0f4d0'
*** First throw call stack:
(0   CoreFoundation                      0x0000000110be26fb __exceptionPreprocess + 3311   libobjc.A.dylib                     0x0000000110186ac5 objc_exception_throw + 482   CoreFoundation                      0x0000000110c00ab4 -[NSObject(NSObject) doesNotRecognizeSelector:] + 1323   UIKitCore                           0x000000011397cc3d -[UIResponder doesNotRecognizeSelector:] + 2874   CoreFoundation                      0x0000000110be7443 ___forwarding___ + 14435   CoreFoundation                      0x0000000110be9238 _CF_forwarding_prep_0 + 1206   AvoidBtnRepeatClick                 0x000000010f8af1cb -[UIButton(Extension) cs_sendAction:to:forEvent:] + 917   UIKitCore                           0x00000001133a7f36 -[UIControl _sendActionsForEvents:withEvent:] + 4508   UIKitCore                           0x00000001133a6eec -[UIControl touchesEnded:withEvent:] + 5839   UIKitCore                           0x000000011398aeee -[UIWindow _sendTouchesForEvent:] + 254710  UIKitCore                           0x000000011398c5d2 -[UIWindow sendEvent:] + 407911  UIKitCore                           0x000000011396ad16 -[UIApplication sendEvent:] + 35612  UIKitCore                           0x0000000113a3b293 __dispatchPreprocessedEventFromEventQueue + 323213  UIKitCore                           0x0000000113a3dbb9 __handleEventQueueInternal + 591114  CoreFoundation                      0x0000000110b49be1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 1715  CoreFoundation                      0x0000000110b49463 __CFRunLoopDoSources0 + 24316  CoreFoundation                      0x0000000110b43b1f __CFRunLoopRun + 123117  CoreFoundation                      0x0000000110b43302 CFRunLoopRunSpecific + 62618  GraphicsServices                    0x00000001190d22fe GSEventRunModal + 6519  UIKitCore                           0x0000000113950ba2 UIApplicationMain + 14020  AvoidBtnRepeatClick                 0x000000010f8af500 main + 11221  libdyld.dylib                       0x00000001124c1541 start + 122  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

注意:因为在UIButton+Extension.m中的+load方法中交换了UIControlsendAction:to:forEvent:方法,所以在使用UIControl或其子类(比如UISlider)sendAction:to:forEvent:方法时会引起参数缺失的崩溃。可以将UIButton+Extension改成UIControl+Extension以避免此问题。

iOS-UIButton防止重复点击(三种办法)相关推荐

  1. Web应用中避免Form重复提交的三种方案

    Web应用中避免Form重复提交的三种方案 2007-08-21 18:29 Web应用中重复提交的问题的三种解决方案 前两种是利用javascript,后面一种是在使用Struts的情况下的参考实现 ...

  2. 如何实现3台计算机网络传递文件,两台电脑如何实现对拷,三种办法轻松搞定!...

    原标题:两台电脑如何实现对拷,三种办法轻松搞定! 有时候,我们换电脑了,可能需要在两台电脑之间传送大容量的文件,但是两台电脑之间如何互相传送或者拷贝文件,除了U盘.硬盘.QQ或微信能解决问题,你还知道 ...

  3. android投屏乐视,手机投屏Letv电视的三种办法【乐播投屏】

    手机投屏Letv电视的三种办法[乐播投屏] 发布时间:2019/07/01 00:00 Letv超级电视升级到8.0后,看见有部分乐迷反馈说手机投屏投不上去.可能是因为方法没有用对,下面教你三种办法手 ...

  4. 苹果机内存满开不了机怎么办?三种办法解决这个问题。

    iPhone手机使用比较顺畅,但难免也会遇到因为内存不足而导致无法正常开机的情况,很多小伙伴一开始都不相信iPhone内存不足会开不了机,可是如果不幸遇到了这个问题,又该如何解决呢?接下来,就给大家分 ...

  5. linux下批量替换文件内容的三种办法

    http://www.51testing.com/html/93/316693-815340.html 程序开发中,可能你会经常遇到批量替换文件内容的情况,如果你使用的是linux,那么恭喜你,你可以 ...

  6. C# 读取网页源码的三种办法WebClient、WebRequest、HttpWebRequest

    直接看这三种办法的源码吧, using System; using System.IO; using System.Net;namespace ReadHtml{ class ReadHtml{ st ...

  7. c#中程序以管理员身份运行的三种办法

    三种办法: 一.设置程序本身的属性:勾选"以管理员身份运行此程序",必要时设置"更改所有用户设置-以管理员身份运行此程序",当然这种办法是被动的,也不是最实际的 ...

  8. linux文件损坏怎么修复工具,在Ubuntu操作系统下修复损坏程序包的三种办法

    如果在 Ubuntu 操作系统下出现损坏的程序包,通常有三种办法可以修复它们,分别是:使用 apt 或 apt-get.使用 dpkg 及解除 dpkg 锁,下面为你一一介绍. 背景 apt 是 Ub ...

  9. 用malloc开辟二维数组的三种办法

    第一种办法:用指针数组:首先看一下原理图(以开辟整型二维数组三行四列为例,以下都是): 先看一下用malloc申请一维数组: int *p=(int *)malloc(10*sizeof(int)); ...

最新文章

  1. R语言使用lm构建线性回归模型、并将目标变量对数化(log10)实战:可视化模型预测输出与实际值对比图、可视化模型的残差、模型预测中系统误差的一个例子 、自定义函数计算R方指标和均方根误差RMSE
  2. Java IO基础原理你该知道这些
  3. 【 FPGA 】时钟抖动浅记
  4. 如何采用锂离子电池提高数据中心电源效率
  5. 反思O2O演化的三个时代,大数据与智能化才是未来所在
  6. Qt5中文乱码解决方案
  7. JS高级程序设计笔记——事件(一)
  8. 在Flex中获取一个屏幕截图(Screenshot)并将其传递给ASP.NET
  9. tensorflow 数据归一化_TensorFlow——批量归一化操作
  10. 解决排列组合问题的通用算法(转)
  11. 【转】PCDATA和CDATA的区别究竟是什么呢?
  12. “广” “专”的抉择 -- 个人技术发展之我见!
  13. MATLAB写入Excel文件
  14. 安卓小项目之刀刀人脸识别系统
  15. 按键精灵_提取文字、数字、字母、符号的通用Function
  16. git各种异常问题整理
  17. 请假流程如何快速实现(OA )呢?:Activiti工作流
  18. 乌镇峰会丨容联云:统一AI基础设施 形成AI生产与共享闭环机制
  19. C++:实现量化Piecewise yield曲线测试实例
  20. Design the Web: Add a Twitter Timeline 设计网页:添加Twitter时间轴 Lynda课程中文字幕

热门文章

  1. 提高C++性能的编程技术笔记:标准模板库+测试代码
  2. windows7 64位机上安装配置CUDA7.5(或8.0)+cudnn5.0操作步骤
  3. Leptonica在VS2010中的编译及简单使用举例
  4. vs2008中,创建基于对话框的mfc动态库步骤
  5. 【Ubuntu】ubuntu设置GUI程序自启动
  6. 推荐本人微博及浅谈发博原则
  7. php iis6 安装ssl证书,在IIS下部署SSL证书实现HTTPS
  8. cocos creator 安卓原生平台环境_竞技对抗小游戏单挑篮球开发历程 | Cocos技术派第12期...
  9. 1月23日服务器例行维护更新公告,1月24日服务器例行维护公告(已完成)
  10. python 速度矢量_最近邻搜索4D空间python快速-矢量化