目錄
- 使用場(chǎng)景
- 方法一 設(shè)置
enabled
或userInteractionEnabled
屬性- 方法二 借助
cancelPreviousPerformRequestsWithTarget:selector:object
實(shí)現(xiàn)- 方法三 通過
runtime
交換方法實(shí)現(xiàn)- 注意事項(xiàng)
一 使用場(chǎng)景
在實(shí)際應(yīng)用場(chǎng)景中,有幾個(gè)業(yè)務(wù)場(chǎng)景需要控制UIButton響應(yīng)事件的時(shí)間間隔尖淘。
- 1 當(dāng)點(diǎn)擊按鈕來執(zhí)行網(wǎng)絡(luò)請(qǐng)求時(shí)解孙,若請(qǐng)求耗時(shí)稍長(zhǎng)卡辰,用戶往往會(huì)多次點(diǎn)擊。這樣寇僧,就執(zhí)行了多次請(qǐng)求煌抒,造成資源浪費(fèi)。
- 2 在移動(dòng)終端設(shè)備性能較差時(shí)娄猫,連續(xù)點(diǎn)擊按鈕會(huì)執(zhí)行多次事件(比如push出來多個(gè)viewController)。
- 3 防止暴力點(diǎn)擊生闲。
二 方法一
通過UIButton
的enabled
屬性和userInteractionEnabled
屬性控制按鈕是否可點(diǎn)擊媳溺。此方案在邏輯上比較清晰、易懂碍讯,但具體代碼書寫分散褂删,常常涉及多個(gè)地方。
- 創(chuàng)建按鈕
- (void)drawBtn {
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 100)];
[btn setTitle:@"按鈕點(diǎn)擊" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
// 按鈕不可點(diǎn)擊時(shí),文字顏色置灰
[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];
}
按鈕不可點(diǎn)擊時(shí)冲茸,標(biāo)題顏色置灰,方便對(duì)比
- 點(diǎn)擊事件
- (void)tapBtn:(UIButton *)btn {
NSLog(@"按鈕點(diǎn)擊...");
btn.enabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
btn.enabled = YES;
});
}
運(yùn)行結(jié)果
2019-06-12 23:21:09.039455+0800 AvoidBtnRepeatClick[8102:362250] 按鈕點(diǎn)擊...
2019-06-12 23:21:11.658751+0800 AvoidBtnRepeatClick[8102:362250] 按鈕點(diǎn)擊...
2019-06-12 23:21:14.057510+0800 AvoidBtnRepeatClick[8102:362250] 按鈕點(diǎn)擊...
2019-06-12 23:21:16.254230+0800 AvoidBtnRepeatClick[8102:362250] 按鈕點(diǎn)擊...
2019-06-12 23:21:18.788004+0800 AvoidBtnRepeatClick[8102:362250] 按鈕點(diǎn)擊...
2019-06-12 23:21:21.155584+0800 AvoidBtnRepeatClick[8102:362250] 按鈕點(diǎn)擊...
2019-06-12 23:21:23.389769+0800 AvoidBtnRepeatClick[8102:362250] 按鈕點(diǎn)擊...
每隔2秒執(zhí)行一次方法
方法二
通過 NSObject 的兩個(gè)方法
// 此方法會(huì)在連續(xù)點(diǎn)擊按鈕時(shí)取消之前的點(diǎn)擊事件缅帘,從而只執(zhí)行最后一次點(diǎn)擊事件
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
// 多長(zhǎng)時(shí)間后做某件事情
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
按鈕創(chuàng)建還是上面代碼
- 按鈕點(diǎn)擊事件如下
/** 方法一 */
- (void)tapBtn:(UIButton *)btn {
NSLog(@"按鈕點(diǎn)擊了...");
// 此方法會(huì)在連續(xù)點(diǎn)擊按鈕時(shí)取消之前的點(diǎn)擊事件轴术,從而只執(zhí)行最后一次點(diǎn)擊事件
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClickedAction:) object:btn];
// 多長(zhǎng)時(shí)間后做某件事情
[self performSelector:@selector(buttonClickedAction:) withObject:btn afterDelay:2.0];
}
- (void)buttonClickedAction:(UIButton *)btn {
NSLog(@"真正開始執(zhí)行業(yè)務(wù) - 比如網(wǎng)絡(luò)請(qǐng)求...");
}
- 運(yùn)行結(jié)果
2019-06-13 09:15:58.935540+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:15:59.284096+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:15:59.760772+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:00.238923+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:00.689305+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:02.689633+0800 AvoidBtnRepeatClick[62321:2927724] 真正開始執(zhí)行業(yè)務(wù) - 比如網(wǎng)絡(luò)請(qǐng)求...
2019-06-13 09:16:03.479984+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:03.884124+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:04.334930+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:04.776324+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:05.179153+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:07.179512+0800 AvoidBtnRepeatClick[62321:2927724] 真正開始執(zhí)行業(yè)務(wù) - 比如網(wǎng)絡(luò)請(qǐng)求...
2019-06-13 09:16:08.062850+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:10.064171+0800 AvoidBtnRepeatClick[62321:2927724] 真正開始執(zhí)行業(yè)務(wù) - 比如網(wǎng)絡(luò)請(qǐng)求...
2019-06-13 09:16:10.947205+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:12.948065+0800 AvoidBtnRepeatClick[62321:2927724] 真正開始執(zhí)行業(yè)務(wù) - 比如網(wǎng)絡(luò)請(qǐng)求...
2019-06-13 09:16:13.528897+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:13.776711+0800 AvoidBtnRepeatClick[62321:2927724] 按鈕點(diǎn)擊了...
2019-06-13 09:16:15.777735+0800 AvoidBtnRepeatClick[62321:2927724] 真正開始執(zhí)行業(yè)務(wù) - 比如網(wǎng)絡(luò)請(qǐng)求...
通過打印結(jié)果可知,如果連續(xù)點(diǎn)擊多次钦无,只會(huì)響應(yīng)最后一次點(diǎn)擊事件逗栽,并且是在設(shè)定的時(shí)間間隔后執(zhí)行,這邊設(shè)置的時(shí)間間隔是 2S失暂。
總結(jié):會(huì)出現(xiàn)延時(shí)現(xiàn)象彼宠,并且需要對(duì)大量的
UIButton
做處理,工作量大弟塞,不方便凭峡。
方法三
通過Runtime
交換UIButton
的響應(yīng)事件方法,從而控制響應(yīng)事件的時(shí)間間隔决记。
實(shí)現(xiàn)步驟如下:
- 1 創(chuàng)建一個(gè)
UIButton
的分類摧冀,使用runtime
增加public
屬性cs_eventInterval
和private
屬性cs_eventInvalid
。 - 2 在
+load
方法中使用runtime
將UIButton
的-sendAction:to:forEvent:
方法與自定義的cs_sendAction:to:forEvent:
方法進(jìn)行交換 - 3 使用
cs_eventInterval
作為控制cs_eventInvalid
的計(jì)時(shí)因子,用cs_eventInvalid
控制UIButton
的event
事件是否有效索昂。
*代碼實(shí)現(xiàn)如下
@interface UIButton (Extension)
/** 時(shí)間間隔 */
@property(nonatomic, assign)NSTimeInterval cs_eventInterval;
@end
#import "UIButton+Extension.h"
#import <objc/runtime.h>
static char *const kEventIntervalKey = "kEventIntervalKey"; // 時(shí)間間隔
static char *const kEventInvalidKey = "kEventInvalidKey"; // 是否失效
@interface UIButton()
/** 是否失效 - 即不可以點(diǎn)擊 */
@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);
}
- 測(cè)試代碼如下
/** 方法三 */
- (void)drawExpecialBtn{
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 100)];
[btn setTitle:@"按鈕點(diǎn)擊" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
// 按鈕不可點(diǎn)擊時(shí),文字顏色置灰
[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(@"按鈕點(diǎn)擊...");
}
- 運(yùn)行結(jié)果如下
2019-06-13 19:18:48.314110+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:18:50.346907+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:18:52.512887+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:18:54.515119+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:18:56.577693+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:18:58.679121+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:19:00.681003+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:19:02.752387+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
2019-06-13 19:19:04.879559+0800 AvoidBtnRepeatClick[89795:3312038] 按鈕點(diǎn)擊...
四 注意事項(xiàng)
在方法三中交互UIButton
的sendAction:to:forEvent:
方法,實(shí)際上交互的是UIControl
的sendAction:to:forEvent:
方法建车,所以在使用·UIControl·或其·子類(比如UISlider)·的·sendAction:to:forEvent:·方法時(shí)會(huì)引起參數(shù)缺失的崩潰。
- 測(cè)試代碼如下
/** 注意事項(xiàng) */
- (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點(diǎn)擊...");
}
運(yùn)行結(jié)果
(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)
注意:因?yàn)樵?code>UIButton+Extension.m中的
+load
方法中交換了UIControl
的sendAction:to:forEvent:
方法椒惨,所以在使用UIControl
或其子類(比如UISlider)
的sendAction:to:forEvent:
方法時(shí)會(huì)引起參數(shù)缺失的崩潰缤至。可以將UIButton+Extension
改成UIControl+Extension
以避免此問題康谆。
本文參考
iOS UIButton之防止重復(fù)點(diǎn)擊(控制事件響應(yīng)時(shí)間間隔)
iOS---防止UIButton重復(fù)點(diǎn)擊的三種實(shí)現(xiàn)方式
項(xiàng)目鏈接地址 AvoidBtnRepeatClick