iOS-UIButton防止重复点击(三种办法)
Posted walkerwqp
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 + 331 1 libobjc.A.dylib 0x0000000110186ac5 objc_exception_throw + 48 2 CoreFoundation 0x0000000110c00ab4 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132 3 UIKitCore 0x000000011397cc3d -[UIResponder doesNotRecognizeSelector:] + 287 4 CoreFoundation 0x0000000110be7443 ___forwarding___ + 1443 5 CoreFoundation 0x0000000110be9238 _CF_forwarding_prep_0 + 120 6 AvoidBtnRepeatClick 0x000000010f8af1cb -[UIButton(Extension) cs_sendAction:to:forEvent:] + 91 7 UIKitCore 0x00000001133a7f36 -[UIControl _sendActionsForEvents:withEvent:] + 450 8 UIKitCore 0x00000001133a6eec -[UIControl touchesEnded:withEvent:] + 583 9 UIKitCore 0x000000011398aeee -[UIWindow _sendTouchesForEvent:] + 2547 10 UIKitCore 0x000000011398c5d2 -[UIWindow sendEvent:] + 4079 11 UIKitCore 0x000000011396ad16 -[UIApplication sendEvent:] + 356 12 UIKitCore 0x0000000113a3b293 __dispatchPreprocessedEventFromEventQueue + 3232 13 UIKitCore 0x0000000113a3dbb9 __handleEventQueueInternal + 5911 14 CoreFoundation 0x0000000110b49be1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 15 CoreFoundation 0x0000000110b49463 __CFRunLoopDoSources0 + 243 16 CoreFoundation 0x0000000110b43b1f __CFRunLoopRun + 1231 17 CoreFoundation 0x0000000110b43302 CFRunLoopRunSpecific + 626 18 GraphicsServices 0x00000001190d22fe GSEventRunModal + 65 19 UIKitCore 0x0000000113950ba2 UIApplicationMain + 140 20 AvoidBtnRepeatClick 0x000000010f8af500 main + 112 21 libdyld.dylib 0x00000001124c1541 start + 1 22 ??? 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防止重复点击(三种办法)的主要内容,如果未能解决你的问题,请参考以下文章