iOS---防止UIButton重復(fù)點(diǎn)擊的三種實(shí)現(xiàn)方式

通常, 我們會采用如下的一些措施來防止重復(fù)點(diǎn)擊UIButton:

使用UIButton的enabled或userInteractionEnabled

使用UIButton的enabled屬性, 在點(diǎn)擊后, 禁止UIButton的交互, 直到完成指定任務(wù)之后再將其enable即可.

[btn addTarget:self action:@selector(actionFixMultiClick_enabled:) forControlEvents:UIControlEventTouchUpInside];

// xxx

- (void)actionFixMultiClick_enabled:(UIButton *)sender {
    sender.enabled = NO;
    [self btnClickedOperations];
}

- (void)btnClickedOperations {
    self.view.backgroundColor = [UIColor colorWithRed:((arc4random() % 255) / 255.0)
                                                green:((arc4random() % 255) / 255.0)
                                                 blue:((arc4random() % 255) / 255.0)
                                                alpha:1.0f];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"btnClickedOperations");
        btn.enabled = YES;
    });
}

使用performSelector:withObject:afterDelay:和cancelPreviousPerformRequestsWithTarget

使用這種方式, 會在連續(xù)重復(fù)點(diǎn)擊UIButton的時(shí)候, 自動(dòng)取消掉之前的操作, 延時(shí)1s后執(zhí)行實(shí)際的操作.
這樣, 看起來會比第一種和第三種稍微延遲執(zhí)行實(shí)際的操作.

[btn addTarget:self action:@selector(actionFixMultiClick_performSelector:) for

// xxx

- (void)actionFixMultiClick_performSelector:(UIButton *)sender {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(btnClickedOperations) object:nil];

    [self performSelector:@selector(btnClickedOperations) withObject:nil afterDelay:1];
}

使用runtime來對sendAction:to:forEvent:方法進(jìn)行hook

UIControl的sendAction:to:forEvent:方法用于處理事件響應(yīng).
如果我們在該方法的實(shí)現(xiàn)中, 添加針對點(diǎn)擊事件的時(shí)間間隔相關(guān)的處理代碼, 則能夠做到在指定時(shí)間間隔中防止重復(fù)點(diǎn)擊.

首先, 為UIButton添加一個(gè)Category:

@interface UIButton (CS_FixMultiClick)

@property (nonatomic, assign) NSTimeInterval cs_acceptEventInterval; // 重復(fù)點(diǎn)擊的間隔

@property (nonatomic, assign) NSTimeInterval cs_acceptEventTime;

@end

Category不能給類添加屬性, 所以以上的cs_acceptEventInterval和cs_acceptEventTime只會有對應(yīng)的getter和setter方法, 不會添加真正的成員變量.
如果我們不在實(shí)現(xiàn)文件中添加其getter和setter方法, 則采用*** btn.cs_acceptEventInterval = 1; *** 這種方法嘗試訪問該屬性會出錯(cuò).

2016-06-29 14:04:52.538 DemoRuntime[17380:1387981] -[UIButton setCs_acceptEventInterval:]: unrecognized selector sent to instance 0x7fe8e154e470

在實(shí)現(xiàn)文件中通過runtime的關(guān)聯(lián)對象的方式, 為UIButton添加以上兩個(gè)屬性. 代碼如下:

#import "UIControl+CS_FixMultiClick.h"
#import <objc/runtime.h>

@implementation UIButton (CS_FixMultiClick)

// 因category不能添加屬性辖试,只能通過關(guān)聯(lián)對象的方式汇跨。
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";

- (NSTimeInterval)cs_acceptEventInterval {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}

- (void)setCs_acceptEventInterval:(NSTimeInterval)cs_acceptEventInterval {
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(cs_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";

- (NSTimeInterval)cs_acceptEventTime {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];
}

- (void)setCs_acceptEventTime:(NSTimeInterval)cs_acceptEventTime {
    objc_setAssociatedObject(self, UIControl_acceptEventTime, @(cs_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


// 在load時(shí)執(zhí)行hook
+ (void)load {
    Method before   = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method after    = class_getInstanceMethod(self, @selector(cs_sendAction:to:forEvent:));
    method_exchangeImplementations(before, after);
}

- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([NSDate date].timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_acceptEventInterval) {
        return;
    }

    if (self.cs_acceptEventInterval > 0) {
        self.cs_acceptEventTime = [NSDate date].timeIntervalSince1970;
    }

    [self cs_sendAction:action to:target forEvent:event];
}

@end

load方法是在objc庫中的一個(gè)load_images函數(shù)中調(diào)用的. 先把二進(jìn)制映像文件中的頭信息取出, 再解析和讀出各個(gè)模塊中的類定義信息, 把實(shí)現(xiàn)了load方法的類和Category記錄下來, 最后統(tǒng)一執(zhí)行調(diào)用. 主類中的load方法的調(diào)用時(shí)機(jī)要早于Category中的load方法.
關(guān)于load和initialize方法, 可參看博客NSObject的load和initialize方法.
因此, 我們在Category中的load方法中, 執(zhí)行runtime的method swizzling, 即可將UIButton的事件響應(yīng)方法sendAction:to:forEvent:替換為我們自定義的方法cs_sendAction:to:forEvent:.
關(guān)于runtime的關(guān)聯(lián)對象和method swizzling, 這里就不多做介紹了, 可參考博客iOS --- 理解Runtime機(jī)制及其使用場景.
那么, 如何使用呢?

btn.cs_acceptEventInterval = 1;

這樣, 就給UIButton指定了1s的時(shí)間間隔用于防止重復(fù)點(diǎn)擊.

總結(jié)

三種方式中推薦使用runtime方式, 這樣可以為所有的UIButton同時(shí)添加.
有些場景下, 可以考慮使用第二種方式, 自動(dòng)延遲后以最終的一次點(diǎn)擊事件為準(zhǔn)執(zhí)行實(shí)際操作.
Demo請參考DemoRuntime.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗦明,隨后出現(xiàn)的幾起案子府瞄,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灸叼,死亡現(xiàn)場離奇詭異神汹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)古今,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門屁魏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捉腥,你說我怎么就攤上這事氓拼。” “怎么了抵碟?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵桃漾,是天一觀的道長。 經(jīng)常有香客問我拟逮,道長撬统,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任敦迄,我火速辦了婚禮恋追,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罚屋。我一直安慰自己苦囱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布脾猛。 她就那樣靜靜地躺著撕彤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尖滚。 梳的紋絲不亂的頭發(fā)上喉刘,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音漆弄,去河邊找鬼睦裳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撼唾,可吹牛的內(nèi)容都是我干的廉邑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼倒谷,長吁一口氣:“原來是場噩夢啊……” “哼蛛蒙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起渤愁,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牵祟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后抖格,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诺苹,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咕晋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了收奔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掌呜。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖坪哄,靈堂內(nèi)的尸體忽然破棺而出质蕉,到底是詐尸還是另有隱情,我是刑警寧澤翩肌,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布模暗,位于F島的核電站,受9級特大地震影響摧阅,放射性物質(zhì)發(fā)生泄漏汰蓉。R本人自食惡果不足惜绷蹲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一棒卷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祝钢,春花似錦比规、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疤估,卻和暖如春灾常,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铃拇。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工钞瀑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慷荔。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓雕什,卻偏偏與公主長得像,于是被迫代替她去往敵國和親显晶。 傳聞我的和親對象是個(gè)殘疾皇子贷岸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容