使用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
轉載于:https://www.cnblogs.com/jezhuang/p/10338446.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的使用runtime解决棘手问题锦集(持续更新)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rpm批量卸载所有带有Java的文件
- 下一篇: 训练指南 UVALive - 3713