zoukankan      html  css  js  c++  java
  • 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;
            }];
        }
    }
  • 相关阅读:
    Go语言从入门到放弃(三) 布尔/数字/格式化输出
    11. GLOBAL_VARIABLES 与 SESSION_VARIABLES
    10. GLOBAL_STATUS 与 SESSION_STATUS
    9. FILES
    8. EVENTS
    7. ENGINES
    6. COLUMN_PRIVILEGES
    5. COLUMNS
    4. COLLATION_CHARACTER_SET_APPLICABILITY
    3. COLLATIONS
  • 原文地址:https://www.cnblogs.com/wangkejia/p/14255570.html
Copyright © 2011-2022 走看看