使用runtime解决棘手问题锦集(持续更新)

写在最前面:本文章只是记录自己再项目中遇到的问题,并用runtime

 

1.项目需要按钮触发范围比原来布局大,该如何实现?

首先想到的是改变按钮的大小,这个方法是最基础的方法来根本上解决按钮范围问题。可是有的项目一个按钮贴着一个按钮,但是只能中间高亮的按钮才能被点击,大小固定无法改变。这种情况下无法通过改变布局来增加点击范围,我们可以通过runtime运行时机制来动态增加按钮的可点击范围。具体代码如下:

#import “UIButton+MQIntervalClickButton.h”

#import <objc/runtime.h>

 

// 按钮点击间隔时间

static char* const intervalClickTimeKey = “intervalClickTimeKey”;

static char* const canClickButtonKey    = “canClickButtonKey”;

 

// 按钮点击可扩大范围

static char* const expandHitFloatKey    = “expandHitFloatKey”;

 

@interface UIButton ()

 

// 是否可响应点击事件 YES:不会响应点击事件 NO:会响应点击事件

@property (nonatomic, assign) BOOL canClickButton;

 

@end

 

@implementation UIButton (MQIntervalClickButton)

 

#pragma mark – Action

// 交换后按钮的点击事件

– (void)mq_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event

{

    if (!self.canClickButton) {

        

//        // 默认间隔时间为3

//        self.intervalClickTime = self.intervalClickTime == 0?3 : self.intervalClickTime;

        // 第一次执行点击事件后设置是否可点击属性为YES

        self.canClickButton = YES;

        [self mq_sendAction:action to:target forEvent:event];

        // 延迟间隔时间设置是否可点击属性NO

        [self performSelector:@selector(setCanClickButton:) withObject:@(NO) afterDelay:self.intervalClickTime];

    }

}

 

// 重写方法-点击是否在可响应范围内

– (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

{

    if (self.expandHitFloat) {

        

        CGRect buttonFrame = self.bounds;

        // CGRectInset(CGRect rect, CGFloat dx, CGFloat dy)是以rect为中心,根据dx和dy来实现缩小 :正值表示缩小,负值表示扩大

        CGRect hitFrame = CGRectInset(buttonFrame, self.expandHitFloat, self.expandHitFloat);

        

        return CGRectContainsPoint(hitFrame, point);

    } else {

        

        return [super pointInside:point withEvent:event];

    }

}

 

#pragma mark – setter & getter

 

// runtime添加按钮点击间隔时间

– (NSTimeInterval)intervalClickTime

{

    return [objc_getAssociatedObject(self, intervalClickTimeKey) doubleValue];

}

 

– (void)setIntervalClickTime:(NSTimeInterval)intervalClickTime

{

    objc_setAssociatedObject(self, intervalClickTimeKey, @(intervalClickTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

 

// runtime添加按钮是否可点击

– (BOOL)canClickButton

{

    return [objc_getAssociatedObject(self, canClickButtonKey) doubleValue];

}

 

– (void)setCanClickButton:(BOOL)canClickButton

{

    objc_setAssociatedObject(self, canClickButtonKey, @(canClickButton), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

 

// runtime添加点击扩大范围

– (CGFloat)expandHitFloat

{

    return [objc_getAssociatedObject(self, expandHitFloatKey) floatValue];

}

 

– (void)setExpandHitFloat:(CGFloat)expandHitFloat

{

    objc_setAssociatedObject(self, expandHitFloatKey, @(expandHitFloat), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

@end

 

2.使用系统提示框UIAlertController弹出信息,点击确定又不让它消失。没有做修改的情况下,点击确定或者取消UIAlertController直接消失,需求需要点击确认之后用输入框的信息去后台比较反馈之后,如果信息和后台相符则消失如果不符合则不消失弹出toast提示错误并UIAlertController

这种情况使用runtime黑科技很容易实现,首先用在提示框弹出的时候获取到@“_dismissAnimated:triggeringAction:triggeredByPopoverDimmingView:dismissCompletion:”系统消失方法,添加一个控制消失与否的BOOL值zje_rejectDismiss用来判断什么时候可以消失,具体代码如下:

.h

#import <UIKit/UIKit.h>

 

@interface UIAlertController (HPHDismiss)

@property (nonatomic, assign) BOOL zje_rejectDismiss;

@end

 

.m

#import “UIAlertController+HPHDismiss.h”

#import <objc/runtime.h>

 

@implementation UIAlertController (HPHDismiss)

– (void)setZje_rejectDismiss:(BOOL)zje_rejectDismiss

{

    objc_setAssociatedObject(self, @selector(zje_rejectDismiss), @(zje_rejectDismiss), OBJC_ASSOCIATION_ASSIGN);

}

 

– (BOOL)zje_rejectDismiss

{

    return [(NSNumber *)objc_getAssociatedObject(self, _cmd) boolValue];

}

 

+ (void)load {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        Class class = [self class];

        

        SEL originalSelector = NSSelectorFromString(@”_dismissAnimated:triggeringAction:triggeredByPopoverDimmingView:dismissCompletion:”);

        SEL swizzledSelector = @selector(zje_rejectDismiss:

                                         triggeringAction:

                                         triggeredByPopoverDimmingView:

                                         dismissCompletion:);

        

        Method originalMethod = class_getInstanceMethod(class, originalSelector);

        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        

        //        动态添加方法,如果类中不存在这个方法的实现,则添加成功

        //        这里 UIAlertController 类中存在 originalMethod,所以添加是失败的

        BOOL didAddMethod = class_addMethod(class,

                                            originalSelector,

                                            method_getImplementation(swizzledMethod),

                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {

            // 如果添加成功,则用 originalMethod 替换添加的空方法 originalMethod

            class_replaceMethod(class,

                                swizzledSelector,

                                method_getImplementation(originalMethod),

                                method_getTypeEncoding(originalMethod));

        } else {

            // 交换两个方法的实现

            method_exchangeImplementations(originalMethod, swizzledMethod);

        }

    });

}

– (void) zje_rejectDismiss:(BOOL)animation

          triggeringAction:(UIAlertAction *)action

triggeredByPopoverDimmingView:(id)view

         dismissCompletion:(id)handler {

    //    如果点击“取消”按钮或者允许弹框 dismiss,就调用原来的方法(originalMethod)

    //      因为已经交换了两个方法的实现,所以其实是调用 swizzledMethod

    //      所以这里并不会出现循环调用

    //    否则就忽略原来的方法(originalMethod),直接下一步,掉用后面的方法

    if (action.style == UIAlertActionStyleCancel || self.zje_rejectDismiss == NO) {

        [self zje_rejectDismiss:animation

               triggeringAction:action

  triggeredByPopoverDimmingView:view

              dismissCompletion:handler];

    } else {

        SEL invokeHandler = NSSelectorFromString(@”_invokeHandlersForAction:”);

        //        这里如果使用 performSelector 来调 invokeHandler 这个方法

        //          [self performSelector:invokeHandler withObject:action];

        //        会报 “PerformSelector may cause a leak because its selector is unknown” 的警告

        //        为消除警告,用下面的方法

        IMP imp = [self methodForSelector:invokeHandler];

        void (*func)(id, SEL, UIAlertAction *) = (void *)imp;

        func(self, invokeHandler, action);

    }

}

@end

 

3.避免按钮重复点击。

.h

#import <UIKit/UIKit.h>

 

@interface UIControl (SingleTap)

 

@property (nonatomic, assign) NSTimeInterval cjr_acceptEventInterval;// 可以用这个给重复点击加间隔

 

.m

 

#import “UIControl+SingleTap.h”

#import <objc/runtime.h>

 

 

@implementation UIControl (SingleTap)

 

static const char *UIControl_acceptEventInterval = “UIControl_acceptEventInterval”;

static const char *UIControl_acceptEventTime = “UIControl_acceptEventTime”;

 

– (NSTimeInterval )cjr_acceptEventInterval{

    return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];

}

 

– (void)setCjr_acceptEventInterval:(NSTimeInterval)cjr_acceptEventInterval{

    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(cjr_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

 

– (NSTimeInterval )cjr_acceptEventTime{

    return [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];

}

 

– (void)setCjr_acceptEventTime:(NSTimeInterval)cjr_acceptEventTime{

    objc_setAssociatedObject(self, UIControl_acceptEventTime, @(cjr_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

 

+ (void)load{

    //获取着两个方法

    Method systemMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));

    SEL sysSEL = @selector(sendAction:to:forEvent:);

    

    Method myMethod = class_getInstanceMethod(self, @selector(cjr_sendAction:to:forEvent:));

    SEL mySEL = @selector(cjr_sendAction:to:forEvent:);

    

    //添加方法进去

    BOOL didAddMethod = class_addMethod(self, sysSEL, method_getImplementation(myMethod), method_getTypeEncoding(myMethod));

    

    //如果方法已经存在了

    if (didAddMethod) {

        class_replaceMethod(self, mySEL, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));

    }else{

        method_exchangeImplementations(systemMethod, myMethod);

        

    }

     //———-以上主要是实现两个方法的互换,load是gcd的只shareinstance,果断保证执行一次

}

 

– (void)cjr_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{

    if (NSDate.date.timeIntervalSince1970 – self.cjr_acceptEventTime < self.cjr_acceptEventInterval) {

        return;

    }

    

    if (self.cjr_acceptEventInterval > 0) {

        self.cjr_acceptEventTime = NSDate.date.timeIntervalSince1970;

    }

    

    [self cjr_sendAction:action to:target forEvent:event];

}

@end

 

 

@property (nonatomic, assign) NSTimeInterval cjr_acceptEventTime;// 间隔时间

 

@end