使用runtime解决棘手问题锦集(持续更新)
写在最前面:本文章只是记录自己再项目中遇到的问题,并用runtime
1.项目需要按钮触发范围比原来布局大,该如何实现?
首先想到的是改变按钮的大小,这个方法是最基础的方法来根本上解决按钮范围问题。可是有的项目一个按钮贴着一个按钮,但是只能中间高亮的按钮才能被点击,大小固定无法改变。这种情况下无法通过改变布局来增加点击范围,我们可以通过runtime运行时机制来动态增加按钮的可点击范围。具体代码如下:
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ff4647; background-color: #000000 }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; background-color: #000000; min-height: 14.0px }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #4bd156; background-color: #000000 }
p.p4 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000 }
p.p5 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #de38a5; background-color: #000000 }
p.p6 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #eb905a; background-color: #000000 }
p.p7 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #08fa95; background-color: #000000 }
p.p8 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000; min-height: 13.0px }
p.p9 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #00b1ff; background-color: #000000 }
span.s1 { color: #eb905a }
span.s2 { color: #de38a5 }
span.s3 { color: #ff4647 }
span.s4 { color: #ffffff }
span.s5 { color: #00b1ff }
span.s6 { color: #08fa95 }
span.s7 { color: #8b87ff }
#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
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ff4647; background-color: #000000 }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; background-color: #000000; min-height: 14.0px }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000 }
p.p4 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #de38a5; background-color: #000000 }
span.s1 { color: #eb905a }
span.s2 { color: #de38a5 }
span.s3 { color: #ffffff }
#import <UIKit/UIKit.h>
@interface UIAlertController (HPHDismiss)
@property (nonatomic, assign) BOOL zje_rejectDismiss;
@end
.m
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ff4647; background-color: #000000 }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; background-color: #000000; min-height: 14.0px }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000 }
p.p4 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #00b1ff; background-color: #000000 }
p.p5 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000; min-height: 13.0px }
p.p6 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #4bd156; background-color: #000000 }
p.p7 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #08fa95; background-color: #000000 }
p.p8 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #de38a5; background-color: #000000 }
span.s1 { color: #eb905a }
span.s2 { color: #de38a5 }
span.s3 { color: #00b1ff }
span.s4 { color: #8b87ff }
span.s5 { color: #ffffff }
span.s6 { color: #08fa95 }
span.s7 { color: #4bd156 }
#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
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ff4647; background-color: #000000 }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; background-color: #000000; min-height: 14.0px }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000 }
p.p4 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #de38a5; background-color: #000000 }
span.s1 { color: #eb905a }
span.s2 { color: #de38a5 }
span.s3 { color: #00b1ff }
span.s4 { color: #4bd156 }
#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
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #00b1ff; background-color: #000000 }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #00b1ff; background-color: #000000 }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ff4647; background-color: #000000 }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000 }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; background-color: #000000; min-height: 14.0px }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ff4647; background-color: #000000 }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000 }
p.p4 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #00b1ff; background-color: #000000 }
p.p5 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #4bd156; background-color: #000000 }
p.p6 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; background-color: #000000; min-height: 13.0px }
p.p7 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #08fa95; background-color: #000000 }
p.p8 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #de38a5; background-color: #000000 }
span.s1 { color: #eb905a }
span.s2 { color: #de38a5 }
span.s3 { color: #ff4647 }
span.s4 { color: #00b1ff }
span.s5 { color: #ffffff }
span.s6 { color: #08fa95 }
span.s7 { color: #8b87ff }