iOS-用Runtime解决Button重复点击引发的相机按钮问题

引入

在项目中经常能用到一个功能,就是对于按钮的点击时间间隔控制,如果不控制,什么时候点击都会触发事件,一般一秒内允许按钮点击1到3次,这里就需要用Runtime实现,下面是我基于UIButton创建的一个分类:

.h

#import <UIKit/UIKit.h>

@interface UIButton (XKButton)
/**
 *  为按钮添加点击间隔 eventTimeInterval秒
 */
@property (nonatomic, assign) NSTimeInterval eventTimeInterval;
@end

.m

#import "UIButton+XKButton.h"
#import <objc/runtime.h>
#define xkDefaultClickInterval 0.5  //默认时间间隔
@interface UIButton ()
/**
 *  bool YES 忽略点击事件   NO 允许点击事件
 */
@property (nonatomic, assign) BOOL isIgnoreEvent;
@end
@implementation UIButton (XKButton)

static const char * UIControl_eventTimeInterval = "UIControl_eventTimeInterval";
static const char * UIControl_enventIsIgnoreEvent = "UIControl_enventIsIgnoreEvent";
// runtime 动态绑定 属性
- (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent{
    objc_setAssociatedObject(self, UIControl_enventIsIgnoreEvent, @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isIgnoreEvent{
    return [objc_getAssociatedObject(self, UIControl_enventIsIgnoreEvent) boolValue];
}

- (NSTimeInterval)eventTimeInterval{
    return [objc_getAssociatedObject(self, UIControl_eventTimeInterval) doubleValue];
}

- (void)setEventTimeInterval:(NSTimeInterval)eventTimeInterval{
    objc_setAssociatedObject(self, UIControl_eventTimeInterval, @(eventTimeInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (void)load{
    // Method Swizzling
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL selA = @selector(sendAction:to:forEvent:);
        SEL selB = @selector(_mqbd_sendAction:to:forEvent:);
        Method methodA = class_getInstanceMethod(self,selA);
        Method methodB = class_getInstanceMethod(self, selB);
        BOOL isAdd = class_addMethod(self, selA, method_getImplementation(methodB), method_getTypeEncoding(methodB));
        if (isAdd) {
            class_replaceMethod(self, selB, method_getImplementation(methodA), method_getTypeEncoding(methodA));
        }else{
            //添加失败了 说明本类中有methodB的实现,此时只需要将methodA和methodB的IMP互换一下即可。
            method_exchangeImplementations(methodA, methodB);
        }
    });
}

- (void)_mqbd_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    self.eventTimeInterval = self.eventTimeInterval == 0 ? xkDefaultClickInterval : self.eventTimeInterval;
    
    if (self.isIgnoreEvent){
        return;
    }else if (self.eventTimeInterval > 0){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.eventTimeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self setIsIgnoreEvent:NO];
        });
    }
    self.isIgnoreEvent = YES;
    [self _mqbd_sendAction:action to:target forEvent:event];
}
@end

通过上述方法,可解决按钮重复点击,默认设置0.5秒内可点击一次;

但是在我调用相机 UIImagePickerController 的时候,问题出现了,点击拍照按钮,无反应,点击切换前后摄像头按钮,无反应,通过测试,发现是上面的文件内容导致。

问题处理

基于 UIImagePickerController 新建一个 XKBaseUIImagePickerController,调用相机时用 XKBaseUIImagePickerController

在 XKBaseUIImagePickerController 的 viewWillAppear 方法中,找到相机上的这几个按钮,然后对按钮添加 accessibilityIdentifier 标识: 

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    UIView *cameraView = [self findView:self.view withName:@"CAMCameraViewControllerContainerView"];
    UIView *cropOverlay = [self findView:cameraView withName:@"CAMViewfinderView"];
    UIView *bottomBar = [self findView:cropOverlay withName:@"CAMBottomBar"];
    for (UIView *tmpView in bottomBar.subviews) {
        if ([NSStringFromClass([tmpView class]) isEqualToString:@"CUShutterButton"] || ///拍照按钮
            [NSStringFromClass([tmpView class]) isEqualToString:@"CAMFlipButton"] ||   ///切换摄像头按钮
            [NSStringFromClass([tmpView class]) isEqualToString:@"CAMReviewButton"]) { ///取消按钮
            UIButton *shutButton = (UIButton *)tmpView;
            ///给按钮添加 accessibilityIdentifier
            shutButton.accessibilityIdentifier = mqb_filterIdentiferButtonIgnoreEvent;
            
        }
    }
}

-(UIView *)findView:(UIView *)aView withName:(NSString *)name{
    Class cl = [aView class];
    NSString *desc = [cl description];
    if ([name isEqualToString:desc])
        return aView;
    for (NSUInteger i = 0; i < [aView.subviews count]; i++)
    {
        UIView *subView = [aView.subviews objectAtIndex:i];
        subView = [self findView:subView withName:name];
        if (subView)
            return subView;
    }
    return nil;
}

在给相机上各按钮添加标识后,在上面的 UIButton 分类文件 .m 文件中

修改以下方法,过滤掉添加标识的按钮即可

- (void)_mqbd_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    self.eventTimeInterval = self.eventTimeInterval == 0 ? mqbDefaultClickInterval : self.eventTimeInterval;
    /// 过滤添加标识的按钮
    if ([self.accessibilityIdentifier isEqualToString:mqb_filterIdentiferButtonIgnoreEvent]) {
        self.eventTimeInterval = 0.f;
        self.isIgnoreEvent = NO;
    }
    else{
        if (self.isIgnoreEvent){
            return;
        }else if (self.eventTimeInterval > 0){
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.eventTimeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self setIsIgnoreEvent:NO];
            });
        }
        self.isIgnoreEvent = YES;
    }
    
    [self _mqbd_sendAction:action to:target forEvent:event];
}
上一篇:IOS14.3开发之使用纯代码创建UIButton以及弹框的使用


下一篇:unity3d UIButton添加点击事件