生活就像海洋,只有意志坚强的人,才能到达彼岸。
前言
为什么UIButton
要防止连续点击?
应用场景如下所示但不仅仅如此:
由于手机性能等其他原因,用户在点击UIButton
进行页面Push
操作时,由于误操作连点了,导致Push
多个相同页面;
当用户点击UIButton
需要请求网络,由于网络请求颇耗时,如果连续点击,意味着需要执行多次相同操作的网络请求,造成服务器资源浪费;
防止暴力点击。
正文
防止UIButton
连续点击的方案概述
-
方案一:通过
UIButton
的enabled
和userInteractionEnabled
两个属性来控制UIButton
是否可点击。 -
方案二:通过
NSObject
以下两个方法来控制UIButton
的响应事件事件间隔。+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector: (SEL)aSelector object:(id)anArgument; - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
-
方案三:通过
Runtime
来控制UIButton
的响应事件的时间间隔。
各个方案的优缺点
方案一
优点:逻辑最清晰,易懂;
缺点:由于属性值是Bool
类型的,所以需要成对出现,代码书写的比较分散,容易出现异常。
方案二
优点:逻辑比较清晰;
缺点:连续点击按钮时取消之前的点击事件,从而只执行最后一次点击事件,所以可能出现延迟现象,代码书写也比较分散。
方案三
利用Runtime
使用类别来处理UIButton
,代码书写比较集中,修改编辑比较方便。
各个方案的代码实现
方案一
@IBAction func onClickedBtn(_ sender: UIButton) {
sender.isEnabled = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
sender.isEnabled = true
}
}
方案二
@IBAction func onClickedBtn(_ sender: UIButton) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(onClickedBtn(_:)), object: sender)
self.perform(#selector(onClickedBtn(_:)), with: sender, afterDelay: 2.0)
}
方案三
public extension UIButton {
private struct AssociatedKeys {
static var eventInterval = "eventInterval"
static var eventUnavailable = "eventUnavailable"
}
/// 重复点击的时间 属性设置
var eventInterval: TimeInterval {
get {
if let interval = objc_getAssociatedObject(self, &AssociatedKeys.eventInterval) as? TimeInterval {
return interval
}
return 0.5
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.eventInterval, newValue as TimeInterval, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/// 按钮不可点 属性设置
private var eventUnavailable : Bool {
get {
if let unavailable = objc_getAssociatedObject(self, &AssociatedKeys.eventUnavailable) as? Bool {
return unavailable
}
return false
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.eventUnavailable, newValue as Bool, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/// 新建初始化方法,在这个方法中实现在运行时方法替换
class func initializeMethod() {
let selector = #selector(UIButton.sendAction(_:to:for:))
let newSelector = #selector(new_sendAction(_:to:for:))
let method: Method = class_getInstanceMethod(UIButton.self, selector)!
let newMethod: Method = class_getInstanceMethod(UIButton.self, newSelector)!
if class_addMethod(UIButton.self, selector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)) {
class_replaceMethod(UIButton.self, newSelector, method_getImplementation(method), method_getTypeEncoding(method))
} else {
method_exchangeImplementations(method, newMethod)
}
}
/// 在这个方法中
@objc private func new_sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
if eventUnavailable == false {
eventUnavailable = true
new_sendAction(action, to: target, for: event)
// 延时
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + eventInterval, execute: {
self.eventUnavailable = false
})
}
}
}
注意:
如果是使用方案三的话,由于在Swift 4
中的initialize()已经被废弃,所以需要在AppDelegate
调用自己写的initializeMethod()
方法。
网友评论