iOS-UIButton防止重复点击(三种办法)
目录
- 使用场景
- 方法一 设置
enabled
或userInteractionEnabled
属性- 方法二 借助
cancelPreviousPerformRequestsWithTarget:selector:object
实现- 方法三 通过
runtime
交换方法实现- 注意事项
一 使用场景
在实际应用场景中,有几个业务场景需要控制UIButton响应事件的时间间隔。
- 1 当点击按钮来执行网络请求时,若请求耗时稍长,用户往往会多次点击。这样,就执行了多次请求,造成资源浪费。
- 2 在移动终端设备性能较差时,连续点击按钮会执行多次事件(比如push出来多个viewController)。
- 3 防止暴力点击。
二 方法一
通过
UIButton
的enabled
属性和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_eventInterval
和private
属性cs_eventInvalid
。- 2 在
+load
方法中使用runtime
将UIButton
的-sendAction:to:forEvent:
方法与自定义的cs_sendAction:to:forEvent:
方法进行交换- 3 使用
cs_eventInterval
作为控制cs_eventInvalid
的计时因子,用cs_eventInvalid
控制UIButton
的event
事件是否有效。*代码实现如下
@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] 按钮点击...
四 注意事项
在方法三中交互
UIButton
的sendAction:to:forEvent:
方法,实际上交互的是UIControl
的sendAction: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
方法中交换了UIControl
的sendAction:to:forEvent:
方法,所以在使用UIControl
或其子类(比如UISlider)
的sendAction:to:forEvent:
方法时会引起参数缺失的崩溃。可以将UIButton+Extension
改成UIControl+Extension
以避免此问题。
iOS-UIButton防止重复点击(三种办法)相关推荐
- Web应用中避免Form重复提交的三种方案
Web应用中避免Form重复提交的三种方案 2007-08-21 18:29 Web应用中重复提交的问题的三种解决方案 前两种是利用javascript,后面一种是在使用Struts的情况下的参考实现 ...
- 如何实现3台计算机网络传递文件,两台电脑如何实现对拷,三种办法轻松搞定!...
原标题:两台电脑如何实现对拷,三种办法轻松搞定! 有时候,我们换电脑了,可能需要在两台电脑之间传送大容量的文件,但是两台电脑之间如何互相传送或者拷贝文件,除了U盘.硬盘.QQ或微信能解决问题,你还知道 ...
- android投屏乐视,手机投屏Letv电视的三种办法【乐播投屏】
手机投屏Letv电视的三种办法[乐播投屏] 发布时间:2019/07/01 00:00 Letv超级电视升级到8.0后,看见有部分乐迷反馈说手机投屏投不上去.可能是因为方法没有用对,下面教你三种办法手 ...
- 苹果机内存满开不了机怎么办?三种办法解决这个问题。
iPhone手机使用比较顺畅,但难免也会遇到因为内存不足而导致无法正常开机的情况,很多小伙伴一开始都不相信iPhone内存不足会开不了机,可是如果不幸遇到了这个问题,又该如何解决呢?接下来,就给大家分 ...
- linux下批量替换文件内容的三种办法
http://www.51testing.com/html/93/316693-815340.html 程序开发中,可能你会经常遇到批量替换文件内容的情况,如果你使用的是linux,那么恭喜你,你可以 ...
- C# 读取网页源码的三种办法WebClient、WebRequest、HttpWebRequest
直接看这三种办法的源码吧, using System; using System.IO; using System.Net;namespace ReadHtml{ class ReadHtml{ st ...
- c#中程序以管理员身份运行的三种办法
三种办法: 一.设置程序本身的属性:勾选"以管理员身份运行此程序",必要时设置"更改所有用户设置-以管理员身份运行此程序",当然这种办法是被动的,也不是最实际的 ...
- linux文件损坏怎么修复工具,在Ubuntu操作系统下修复损坏程序包的三种办法
如果在 Ubuntu 操作系统下出现损坏的程序包,通常有三种办法可以修复它们,分别是:使用 apt 或 apt-get.使用 dpkg 及解除 dpkg 锁,下面为你一一介绍. 背景 apt 是 Ub ...
- 用malloc开辟二维数组的三种办法
第一种办法:用指针数组:首先看一下原理图(以开辟整型二维数组三行四列为例,以下都是): 先看一下用malloc申请一维数组: int *p=(int *)malloc(10*sizeof(int)); ...
最新文章
- R语言使用lm构建线性回归模型、并将目标变量对数化(log10)实战:可视化模型预测输出与实际值对比图、可视化模型的残差、模型预测中系统误差的一个例子 、自定义函数计算R方指标和均方根误差RMSE
- Java IO基础原理你该知道这些
- 【 FPGA 】时钟抖动浅记
- 如何采用锂离子电池提高数据中心电源效率
- 反思O2O演化的三个时代,大数据与智能化才是未来所在
- Qt5中文乱码解决方案
- JS高级程序设计笔记——事件(一)
- 在Flex中获取一个屏幕截图(Screenshot)并将其传递给ASP.NET
- tensorflow 数据归一化_TensorFlow——批量归一化操作
- 解决排列组合问题的通用算法(转)
- 【转】PCDATA和CDATA的区别究竟是什么呢?
- “广” “专”的抉择 -- 个人技术发展之我见!
- MATLAB写入Excel文件
- 安卓小项目之刀刀人脸识别系统
- 按键精灵_提取文字、数字、字母、符号的通用Function
- git各种异常问题整理
- 请假流程如何快速实现(OA )呢?:Activiti工作流
- 乌镇峰会丨容联云:统一AI基础设施 形成AI生产与共享闭环机制
- C++:实现量化Piecewise yield曲线测试实例
- Design the Web: Add a Twitter Timeline 设计网页:添加Twitter时间轴 Lynda课程中文字幕
热门文章
- 提高C++性能的编程技术笔记:标准模板库+测试代码
- windows7 64位机上安装配置CUDA7.5(或8.0)+cudnn5.0操作步骤
- Leptonica在VS2010中的编译及简单使用举例
- vs2008中,创建基于对话框的mfc动态库步骤
- 【Ubuntu】ubuntu设置GUI程序自启动
- 推荐本人微博及浅谈发博原则
- php iis6 安装ssl证书,在IIS下部署SSL证书实现HTTPS
- cocos creator 安卓原生平台环境_竞技对抗小游戏单挑篮球开发历程 | Cocos技术派第12期...
- 1月23日服务器例行维护更新公告,1月24日服务器例行维护公告(已完成)
- python 速度矢量_最近邻搜索4D空间python快速-矢量化