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 ? xkDefaultClickInterval : 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];
}

 其他

关于相机调用时,用水管检测会有内存泄露,在相机 dismiss 后,将相机置 nil,水管就检测不到了:

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    if ([picker isKindOfClass:[UIImagePickerController class]]) {
        [picker dismissViewControllerAnimated:YES completion:^{
            _cameraPicker = nil;
        }];
    }
}
原文地址:https://www.cnblogs.com/wangkejia/p/14255570.html