iOS-UIButton防止重復(fù)點(diǎn)擊(三種辦法)

目錄
  • 使用場(chǎng)景
  • 方法一 設(shè)置enableduserInteractionEnabled屬性
  • 方法二 借助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)擊生闲。
二 方法一

通過UIButtonenabled屬性和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é)果

1.gif
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é)果
1.gif
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_eventIntervalprivate屬性cs_eventInvalid
  • 2 在+load方法中使用runtimeUIButton-sendAction:to:forEvent:方法與自定義的cs_sendAction:to:forEvent:方法進(jìn)行交換
  • 3 使用cs_eventInterval作為控制cs_eventInvalid的計(jì)時(shí)因子,用cs_eventInvalid控制UIButtonevent事件是否有效索昂。

*代碼實(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é)果如下
1.gif
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)

在方法三中交互UIButtonsendAction:to:forEvent:方法,實(shí)際上交互的是UIControlsendAction: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é)果

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) 

注意:因?yàn)樵?code>UIButton+Extension.m中的+load方法中交換了UIControlsendAction: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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末领斥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子秉宿,更是在濱河造成了極大的恐慌戒突,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件描睦,死亡現(xiàn)場(chǎng)離奇詭異膊存,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)忱叭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門隔崎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人韵丑,你說我怎么就攤上這事爵卒。” “怎么了撵彻?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵钓株,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我陌僵,道長(zhǎng)轴合,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任碗短,我火速辦了婚禮受葛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘偎谁。我一直安慰自己总滩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布巡雨。 她就那樣靜靜地躺著闰渔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铐望。 梳的紋絲不亂的頭發(fā)上澜建,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天向挖,我揣著相機(jī)與錄音,去河邊找鬼炕舵。 笑死何之,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咽筋。 我是一名探鬼主播溶推,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奸攻!你這毒婦竟也來了蒜危?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤睹耐,失蹤者是張志新(化名)和其女友劉穎辐赞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硝训,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡响委,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窖梁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赘风。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纵刘,靈堂內(nèi)的尸體忽然破棺而出邀窃,到底是詐尸還是另有隱情,我是刑警寧澤假哎,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布瞬捕,位于F島的核電站,受9級(jí)特大地震影響舵抹,放射性物質(zhì)發(fā)生泄漏肪虎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一掏父、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秆剪,春花似錦赊淑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至洁灵,卻和暖如春饱岸,著一層夾襖步出監(jiān)牢的瞬間掺出,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工苫费, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留汤锨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓百框,卻偏偏與公主長(zhǎng)得像闲礼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铐维,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355