級別:★★☆☆☆
標(biāo)簽:「UIButton Runtime」「UIButton點擊頻率」「UIButton防止多次點擊」
作者: Xs·H
審校: QiShare團隊
有幾個實際業(yè)務(wù)場景需要控制UIButton
響應(yīng)事件的時間間隔。比如:
1、當(dāng)通過點擊按鈕來執(zhí)行網(wǎng)絡(luò)請求時,若請求耗時稍長,用戶往往會再點一次。這樣,就執(zhí)行了兩次請求,造成了資源浪費。
2、在移動終端性能較差時(比如iPhone 6
升級到iOS 11
??),連續(xù)點擊按鈕會執(zhí)行多次事件(比如push出來多個viewController
)。
3、防止暴力點擊。
控制按鈕響應(yīng)事件時間間隔的方案不止一種。比如:
- 方案 1:通過
UIButton
的enabled
屬性和userInteractionEnabled
屬性控制按鈕是否可點擊。此方案在邏輯上比較清晰、易懂,但具體代碼書寫分散,常常涉及多個方法。
- (void)buttonClicked:(UIButton *)sender {
sender.enabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
sender.enabled = YES;
});
}
- 方案2:通過NSObject的
+cancelPreviousPerformRequestsWithTarget:selector:object:
方法和-performSelector:withObject:afterDelay:
方法控制按鈕的響應(yīng)事件的執(zhí)行時間間隔。此方案會在連續(xù)點擊按鈕時取消之前的點擊事件,從而只執(zhí)行最后一次點擊事件,會出現(xiàn)延遲現(xiàn)象。
- (void)buttonClicked:(UIButton *)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClickedAction:) object:sender];
[self performSelector:@selector(buttonClickedAction:) withObject:sender afterDelay:2.0];
}
在需要對大量UIButton
做控制的場景中,方案1和方案2會比較不方便。針對此場景,著重說一下方案3。
-
方案3:通過Runtime控制UIButton響應(yīng)事件的時間間隔。思路如下:
1、創(chuàng)建一個UIButton
的類別,使用runtime
為UIButton
增加public
屬性qi_eventInterval
和private
屬性eventUnavailable
。
2、在+load方法中使用runtime
將UIButton
的-sendAction:to:forEvent:
方法與自定義的-qi_sendAction:to:forEvent:
方法交換Implementation
。
3、使用qi_eventInterval
作為控制eventUnavailable
的計時因子,用eventUnavailable
開控制UIButton
的event
事件是否有效。
方案3可以對所有UIButton生效,具體實現(xiàn)代碼如下:
@interface UIButton (QiEventInterval)
@property (nonatomic, assign) NSTimeInterval qi_eventInterval;
@end
#import "UIButton+QiEventInterval.h"
#import <objc/runtime.h>
static char * const qi_eventIntervalKey = "qi_eventIntervalKey";
static char * const eventUnavailableKey = "eventUnavailableKey";
@interface UIButton ()
@property (nonatomic, assign) BOOL eventUnavailable;
@end
@implementation UIButton (QiEventInterval)
+ (void)load {
Method method = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method qi_method = class_getInstanceMethod(self, @selector(qi_sendAction:to:forEvent:));
method_exchangeImplementations(method, qi_method);
}
#pragma mark - Action functions
//- (void)qi_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// if (self.eventUnavailable == NO) {
// self.eventUnavailable = YES;
// [self qi_sendAction:action to:target forEvent:event];
// [self performSelector:@selector(setEventUnavailable:) withObject:@(NO) afterDelay:self.qi_eventInterval];
// }
//}
- (void)qi_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
if([self isMemberOfClass:[UIButton class]]) {
if (self.eventUnavailable == NO) {
self.eventUnavailable = YES;
[self qi_sendAction:action to:target forEvent:event];
[self performSelector:@selector(setEventUnavailable:) withObject:0 afterDelay:self.qi_eventInterval];
}
} else {
[self qi_sendAction:action to:target forEvent:event];
}
}
#pragma mark - Setter & Getter functions
- (NSTimeInterval)qi_eventInterval {
return [objc_getAssociatedObject(self, qi_eventIntervalKey) doubleValue];
}
- (void)setQi_eventInterval:(NSTimeInterval)qi_eventInterval {
objc_setAssociatedObject(self, qi_eventIntervalKey, @(qi_eventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)eventUnavailable {
return [objc_getAssociatedObject(self, eventUnavailableKey) boolValue];
}
- (void)setEventUnavailable:(BOOL)eventUnavailable {
objc_setAssociatedObject(self, eventUnavailableKey, @(eventUnavailable), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
使用方法:
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
/* here is some button's configuration codes */
[self.view addSubview:button];
//! 設(shè)置按鈕的點擊響應(yīng)間隔時間
button.qi_eventInterval = 2.0;
效果展示:
-
默認(rèn)Button點擊效果:
不設(shè)置qi_eventInterval (默認(rèn)為0) -
設(shè)置qi_eventInterval為2秒:
設(shè)置qi_eventInterval為2秒
PS:針對方案3,因為在
UIButton+QiEventInterval.m
中的+load
方法中交換了UIControl
的sendAction:to:forEvent:
方法,所以在使用UIControl
或其子類(比如UISlider
)的sendAction:to:forEvent:
方法時會引起參數(shù)缺失的崩潰。可以將UIButton+QiEventInterval
改成UIControl+QiEventInterval
以避免此問題。
可從Github獲取工程源碼
關(guān)注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)