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
以避免此问题。
相关文章:
华为诺亚方舟开源预训练模型“哪吒”,4项任务均达到SOTA
出品 | AI科技大本营(ID:rgznai100)BERT之后,新的预训练语言模型XLnet、RoBERTa、ERNIE不断推出,这次,华为诺亚方舟实验室开源了基于BERT的中文预训练语言模型NEZHA(哪吒),寓意模型能…

音量调节助手(转)
源:音量调节助手 软件名称:音量调节助手 软件功能:通过键盘快捷键快速调节系统主音量 软件版本:V2014 软件作者:易几网络 操作系统:所有WINDOWS版本 开发工具:DELPHI XE …

TorchVision中通过AlexNet网络进行图像分类
TorchVision中给出了AlexNet的pretrained模型,模型存放位置为https://download.pytorch.org/models/alexnet-owt-4df8aa71.pth ,可通过models.alexnet函数下载,此函数实现在torchvision/models/alexnet.py中,下载后在Ubuntu上存放…
西湖龙井也上链?是的,以后你喝什么茶我都知道!
什么?区块链还可以帮忙法律取证?是的!就是这么牛13!区块链存证第一案12月9日,据《新华每日电讯》报道,杭州互联网法院用区块链提升审判效率。报道提到一个案例。2018年4月,杭州一家公司…

Java Enumeration接口
import java.util.Vector; import java.util.Enumeration; /* * Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。 * 这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃࿰…
Windows Azure Pack与SCVMM标签解析分享
我在SCVMM上做了好CentOS6.5的VM模板镜像,自己部署也是成功的,现在配置WAP的VM云虚拟机角色配置,在SCVMM上我打好了CentOS6.5的标签,可是在创建虚拟机角色配置中,选择的CentOS却无法找到硬盘,这是怎么回事呢…

Linux下C++中可使用的3种Hook方法
Hook即钩子,截获API调用的技术,是将执行流程重定向到你自己的代码,类似于hack。如使程序运行时调用你自己实现的malloc函数代替调用系统库中的malloc函数。这里介绍下Linux下C中可使用的3中Hook方法: 1. GNU C库允许你通过指定适当…

Java Properties 类
Properties 继承于 Hashtable.表示一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。 Properties 类被许多Java类使用。例如,在获取环境变量时它就作为System.getProperties()方法的返回值。 Properties 定义如下实例变量.这个变量持有一个Properties对…
国产数据库年终大盘点
作者 | 马超 编辑 | 胡巍巍出品 | CSDN(ID:CSDNnews)去“IOE”这个概念,最早由王坚院士在刚刚加入阿里时提出,其目标是将IBM 的小型机、Oracle数据库、EMC存储设备从阿里的IT体系中去除,代之以自主研发的系…

解密FFmpeg播放track mode控制
上一篇文章(http://www.cnblogs.com/yangdanny/p/4421130.html)我们解决了在FFmpeg下如何处理H264和AAC的扩展数据,根据解出的NALU长度恢复了H264的起始码和AAC的ADTS头,这样一般来说播放是没有问题。本篇文章来谈谈如何实现基于FFmpeg的track mode控制&…

UIButton防止按钮和手势的暴力点击
首先理解下几个概念 1、IMP:它是指向一个方法具体实现的指针,每一个方法都有一个对应的IMP,当你发起一个消息之后,最终它会执行的那段代码,就是由IMP这个函数指针指向了这个方法实现的 2、SEL:方法名称的描…

使用Windows7上的VS Code打开远程机Ubuntu上的文件操作步骤
之前在https://blog.csdn.net/fengbingchun/article/details/118991855 中介绍过在Windows10通过VS Code打开Ubuntu 16.04上的文件或文件夹的操作步骤。Windows7上的操作与Windows10有所不同,这里记录下。 Visual Studio Code Remote - SSH扩展允许你在任何远程机器…
微众银行殷磊:AI+卫星,从上帝视角洞察资产管理|BDTC 2019
出品 | AI科技大本营(ID:rgznai100)12月5日-7日,2019中国大数据技术大会(BDTC)于北京隆重举办,大会已成功举办十二届,是大数据领域极具影响力的行业盛会。本届大会汇聚了学术界、企业界上千位知…

【二分答案】【最短路】bzoj1614 [Usaco2007 Jan]Telephone Lines架设电话线
对于二分出的答案x而言,验证答案等价于将所有边权>x的边赋成1,否则赋成0,然后判断从1到n的最短路是否<K。 #include<cstdio> #include<cstring> #include<queue> using namespace std; #define N 1001 #define M 100…

Python3中装饰器@typing.overload的使用
typing.py的源码在:https://github.com/python/cpython/blob/main/Lib/typing.py 。此模块为类型提示(Type Hints)提供运行时支持。这里介绍下typing.overload的使用,从python 3.5版本开始将Typing作为标准库引入。 python3中增加了Function Annotation(…
19年NAACL纪实:自然语言处理的实用性见解 | CSDN博文精选
作者 | Nikita Zhiltsov翻译 | 王威力校对 | 李海明本文为你概述处理不同NLP问题时的具有卓越性能的方法、技术和框架等。计算语言:人类语言技术学会北美分会2019年年会(North American Chapter of the Association for Computational Linguistics: Huma…

高并发场景下数据库的常见问题及解决方案
一、分库分表 (1)为什么要分库分表 随着系统访问量的增加,QPS越来越高,数据库磁盘容量不断增加,一般数据库服务器的QPS在800-1200的时候性能最佳,当超过2000的时候sql就会变得很慢并且很容易被请求打死&a…

典型用户 persona
persona 典型用户 1、姓名:王涛 2、年龄:22 3、收入:基本无收入 4、代表用户在市场上的比例和重要性:王涛为铁道学生。本软件的用户主要是学生和老师,尤其是广大的铁大学子,所以此典型用户的重要性不言而喻…

PyTorch中nn.Module类简介
torch.nn.Module类是所有神经网络模块(modules)的基类,它的实现在torch/nn/modules/module.py中。你的模型也应该继承这个类,主要重载__init__、forward和extra_repr函数。Modules还可以包含其它Modules,从而可以将它们嵌套在树结构中。 只要…

什么是三层交换机、网关、DNS、子网掩码、MAC地址
一、什么是vlan? 二、单臂路由与三层交换机 三、什么是网关 一、什么是网关 二、如何来理解网关 三、网关的ip地址 四、网关是如何实现通信? 五、什么是默认网关? 四、什么是DNS 五、MAC地址 六、子网掩码 很多朋友多次问到什么是网关、dns、子网掩码&…
20行代码发一篇NeurIPS:梯度共享已经不安全了
整理 | 夕颜,Jane出品 | AI科技大本营(ID:rgznai100)【导读】12 月 8 日-14 日,NeurIPS 2019 在加拿大温哥华举行,和往常一样,今年大会吸引了数万名专家参会,并展示了计算机领域的最新进展。其中…

关于页面打印window.print()的样式问题
当我们打印网页的时候。有时候会发现。打印出来的。跟网页上看到的样式的差别有点大。这其中可能有的问题是。样式问题。 当调用打印(window.print())方法时。打印机会在网页的样式中查找 media print{}的样式,并适应到要打印的网页中。 所以 如果要打印的页面符合看…

Python3中参数*args和**kwargs介绍
在Python中,我们可以使用两种特殊符号将可变数量的参数传递给函数:*args和**kwargs。你可以使用任何单词代替args和kwargs,但通常做法是使用args和kwargs。 *args允许函数接受任意数量的位置参数(positional arguments)。 **kwargs收集所有未…
4大主流CPU处理器技术架构,不知道就out了!
作者 | 王艺威责编 | 阿秃RISC(精简指令集计算机)是一种执行较少类型计算机指令的微处理器,起源于80年代的MIPS主机(即RISC机),RISC机中采用的微处理器统称RISC处理器。这样一来,它能够以更快的…

grunt-connect-proxy解决开发时跨域问题
最近的项目中前后端是完全分离开发的,前端用grunt管理项目。这样就会导致一个问题:开发时前端调用后台的接口时因为不在一个服务器,所以会出现跨域问题。但是也不能用JSONP或CROS方式实现真正的跨域,因为项目发布时其实是在同一个…
混合推荐系统就是多个推荐系统“大杂烩”吗?
作者丨gongyouliu编辑丨zandy【导读】在本篇文章中,我们会介绍混合推荐系统(Hybrid Recommender Systems),就是利用多种推荐算法配合起来做推荐,期望避免单个推荐算法存在的问题,最终获得比单个算法更好的推荐效果。本篇文章我们从…

Python3中collections.OrderedDict介绍
Python3中的collections模块实现了特定目标的容器,以提供Python标准内建容器dict、list、set和tuple的替代选择,包括namedtuple、deque、ChainMap、Counter、OrderedDict、defaultdict、UserDict、UserList、UserString。这里介绍下OrderedDictÿ…

汗!雅虎中国个人空间
今天发现雅虎中国有了个人空间,偷偷试了下,让人失望到极点,几乎没有什么特点,和MSN很相似,空间相册放着好好的Flickr不用,偏偏弄了个很垃圾的相册,还有整合能力也不行。都不知道del.icio.us和Fl…

关于v$process与v$session中process的理解
v$session有个process字段,V$PROCESS有个SPID字段,这两个字段是不是一个意思呢?是不是都代表会话的操作系统进程呢?官方文档上的解释:SPID VARCHAR2(12) Operating system process identifierPROCESS VARCHAR2…

Python3中lambda表达式介绍
Python3中的lambda表达式或lambda函数是匿名函数(anonymous function),意味着该函数没有名称。def关键字用于在Python3中创建一个普通函数,类似地,lambda关键字用于在Python3中创建匿名函数。 Python3 lambda函数语法: lambda pa…