0
点赞
收藏
分享

微信扫一扫

iOS 你还在为UIButton 频繁点击而烦恼吗?

在项目中,为了避免按钮被频繁点击,我们一般会操作 UIButton 的可点击状态:enabled,但是如果需要处理的多了,会增加我们开发的工作量,也会增加逻辑不够清晰下的遗漏处理导致按钮无法点击的重大问题,所以我们需要一个可以全局处理 UIButton 时间间隔点击事件的方法,同时可以根据具体的需求,调整时间间隔的时间。

一、为了解决这个需求,我们需要考虑以下几点:

  • UIButton 使用的点击方法,是 UIButton独有的,还是继承于父类?
  • 如果继承于父类,处理父类的点击方法,是否对父类的其他子类有影响?
  • UIButton 有多种 Event,处理的时候是否会同时有多种 Event 有影响?
  • 怎么实现点击的时间间隔?
  • 为了可扩展性,要可以单独设置某个 Button 的时间间隔,以及是否使用增加的时间间隔方法

二、解决办法

  • 针对以上面的思考,我们一一进行解决

三、解决技术

解决这个需求主要用到Runtime的 2 个地方:

注意:里面涉及到几个坑:

四、代码实现解析

Runtime 交换方法图解

分类中属性效果的实现

isKindOfClass & isSubclassOfClass & isMemberOfClass 的区别

使用到的 Runtime 中的方法

  • 获得给定类的指定实例方法
    注意:如果给定的类或者父类没有对应的方法,会返回nil
/** 
 cls:获得哪个类中的方法
 SEL name:获得方法的对象
*/

class_getInstanceMethod(Class  _Nullable __unsafe_unretained cls , SEL  _Nonnull name)

  • 重写 getter 方法
/** 
 object:关联的源对象
 key:关联的 key
*/

objc_getAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>);

  • 重写 setter方法
 /**
 object:关联的源对象
 key:关联的 key
 value:关联对象的值,可以通过将此值置成 nil 来清除关联
 policy:关联的策略
*/
objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)

五、具体代码

注意:

  • sendAction: to: forEvent:
  • addTarget: action: forControlEvents:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIControl (KKClickInterval)
/// 点击事件响应的时间间隔,不设置或者大于 0 时为默认时间间隔
@property (nonatomic, assign) NSTimeInterval clickInterval;
/// 是否忽略响应的时间间隔
@property (nonatomic, assign) BOOL ignoreClickInterval;
+ (void)kk_exchangeClickMethod;

@end

NS_ASSUME_NONNULL_END



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

static double kDefaultInterval = 0.5;

@interface UIControl ()
/// 是否可以点击
@property (nonatomic, assign) BOOL isIgnoreClick;
/// 上次按钮响应的方法名
@property (nonatomic, strong) NSString *oldSELName;
@end

@implementation UIControl (KKClickInterval)

+ (void)kk_exchangeClickMethod {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //  获得方法选择器
        SEL originalSel = @selector(sendAction:to:forEvent:);
        SEL newSel = @selector(kk_sendClickIntervalAction:to:forEvent:);
        //获得方法
        Method originalMethod = class_getInstanceMethod(self , originalSel);
        Method newMethod = class_getInstanceMethod(self , newSel);
        
        //   如果发现方法已经存在,返回NO;也可以用来做检查用,这里是为了避免源方法没有存在的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
        BOOL isAddNewMethod = class_addMethod(self, originalSel, method_getImplementation(newMethod), "v@:");
        if (isAddNewMethod) {
            class_replaceMethod(self, newSel, method_getImplementation(originalMethod), "v@:");
        } else {
            method_exchangeImplementations(originalMethod, newMethod);
        }
    });
}

- (void)kk_sendClickIntervalAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([self isKindOfClass:[UIButton class]] && !self.ignoreClickInterval) {
        if (self.clickInterval <= 0) {
            self.clickInterval = kDefaultInterval;
        };
        
        NSString *currentSELName = NSStringFromSelector(action);
        if (self.isIgnoreClick && [self.oldSELName isEqualToString:currentSELName]) {
            return;
        }
        
        if (self.clickInterval > 0) {
            self.isIgnoreClick = YES;
            self.oldSELName = currentSELName;
            [self performSelector:@selector(kk_ignoreClickState:)
                       withObject:@(NO)
                       afterDelay:self.clickInterval];
        }
    }
    [self kk_sendClickIntervalAction:action to:target forEvent:event];
}

- (void)kk_ignoreClickState:(NSNumber *)ignoreClickState {
    self.isIgnoreClick = ignoreClickState.boolValue;
    self.oldSELName = @"";
}

- (NSTimeInterval)clickInterval {

    return [objc_getAssociatedObject(self, _cmd) doubleValue];
}

- (void)setClickInterval:(NSTimeInterval)clickInterval {
    objc_setAssociatedObject(self, @selector(clickInterval), @(clickInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isIgnoreClick {
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setIsIgnoreClick:(BOOL)isIgnoreClick {
    objc_setAssociatedObject(self, @selector(isIgnoreClick), @(isIgnoreClick), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)ignoreClickInterval {
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setIgnoreClickInterval:(BOOL)ignoreClickInterval {
    objc_setAssociatedObject(self, @selector(ignoreClickInterval), @(ignoreClickInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)oldSELName {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setOldSELName:(NSString *)oldSELName {
    objc_setAssociatedObject(self, @selector(oldSELName), oldSELName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


@end

源文作者:Gavin_Kang
链接:https://juejin.cn/post/6899057632716750855

举报

相关推荐

你还在为“算法”苦恼么?

0 条评论