在开发中经常遇到一些需要弹框 UIAlertController 一直显示不消失的需求,如登录时等。但一般的,不管点击取消或确定按钮,弹框都会消失。今天就用 Runtime 这个“黑魔法”来实现这个需求。
首先在 UIAlertAction 的回调里打个断点,看下 UIAlertController 调用的方法:
可以看到 UIAlertController 调了 _invokeHandlersForAction:
和 _dismissAnimated:triggeringAction:triggeredByPopoverDimmingView:dismissCompletion:
两个方法,可以猜测前一个方法是用来回调 UIAlertAction 的,后一个方法可能与 UIAlertController 的消失与否有关,可以来验证一下。
UIAlertController+Dismiss.h
1 2 3 4 5 6 7 8
| #import <UIKit/UIKit.h>
@interface UIAlertController (Dismiss)
@property (nonatomic, assign) BOOL wy_rejectDismiss;
@end
|
UIAlertController+Dismiss.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #import "UIAlertController+Dismiss.h" #import <objc/runtime.h>
@implementation UIAlertController (Dismiss)
@dynamic wy_rejectDismiss;
- (void)setWy_rejectDismiss:(BOOL)wy_rejectDismiss { objc_setAssociatedObject(self, @selector(wy_rejectDismiss), @(wy_rejectDismiss), OBJC_ASSOCIATION_ASSIGN); }
- (BOOL)wy_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(wy_dismissAnimated: triggeringAction: triggeredByPopoverDimmingView: dismissCompletion:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); }
- (void)wy_dismissAnimated:(BOOL)animation triggeringAction:(UIAlertAction *)action triggeredByPopoverDimmingView:(id)view dismissCompletion:(id)handler {
if (action.style == UIAlertActionStyleCancel || self.wy_rejectDismiss == NO) { [self wy_dismissAnimated:animation triggeringAction:action triggeredByPopoverDimmingView:view dismissCompletion:handler]; } else { SEL invokeHandler = NSSelectorFromString(@"_invokeHandlersForAction:");
IMP imp = [self methodForSelector:invokeHandler]; void (*func)(id, SEL, UIAlertAction *) = (void *)imp; func(self, invokeHandler, action); } }
@end
|
ViewController.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #import "ViewController.h" #import "UIAlertController+Dismiss.h"
@interface ViewController ()
@property (nonatomic, strong) UIAlertController *alertController;
@end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; }
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { [textField addTarget:self action:@selector(handleTextFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged]; }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]; UIAlertAction *confirmActin = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { }]; [alertController addAction:cancelAction]; [alertController addAction:confirmActin]; alertController.wy_rejectDismiss = YES; self.alertController = alertController; [self presentViewController:alertController animated:YES completion:nil]; }
- (void)handleTextFieldEditingChanged:(UITextField *)textField { if ([textField.text isEqualToString:@"123"]) { self.alertController.wy_rejectDismiss = NO; } else { self.alertController.wy_rejectDismiss = YES; } }
@end
|
结果说明猜测是正确的。这样就实现了禁止弹框消失的需求,只需要设置 alertController 的 wy_rejectDismiss
这个属性为 YES
,当需要弹框消失时只要把 wy_rejectDismiss
设置成 NO
即可。
相关链接
Method Swizzling
Runtime 隐藏 Status Bar 背景
performSelector may cause a leak because its selector is unknown