UIAlertView在iOS9后就不建议被使用,官方要求最好使用UIAlertViewController,它集成了UIAlertView和UIActionSheet。UIAlertView和UIActionSheet是建立在View的基础上,调用show方法可以直接显示,并通过代理来实现点击方法的回调,而UIAlertController是建立在UIViewController之上的,需要present显示,并且所有的点击按钮都通过UIAction的model进行创建,当然两者还有很多区别的。今天,我们关注的重点是在项目中有大量使用的UIAlertView和UIActionSheet如何被替换为UIAlertViewController,如果按正常的逻辑:
UIAlertController创建和显示而UIAlertView要先遵循代理:
UIAlertView创建和显示可以看出,如果在项目中有需要将UIAlertView中替换成UIAlertController还是需要做很多工作的。今天,我们来简化一下这个工作,需要写一个UIAlertController的分类,效果是这样:
封装后的UIAlertViewController
可以看到,封装后的可以很方便的兼容UIAlertView的代理方法,而且只需要一行代码。
接下来看封装的头文件
/**
快速创建
*/
static inline UIAlertController *UIAlertControllerCreate( NSString *_Nullable title, NSString *_Nullable message, UIAlertControllerStyle style){
return [UIAlertController alertControllerWithTitle:title message:message preferredStyle:style];
}
static inline UIAlertController *UIAlertControllerAlertCreate(NSString *_Nullable title,NSString *_Nullable message){
return [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
}
static inline UIAlertController *UIAlertControllerSheetCreate(NSString *_Nullable title, NSString *_Nullable message){
return [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleActionSheet];
}
typedef void (^ wtcAlertTapBlock)(NSInteger index, UIAlertAction *action);
@interface UIAlertController (WTCCategory)
/**
添加Action,并设置key值,需要在点击方法中使用
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ addAction)(NSString *title, UIAlertActionStyle style, NSInteger index);
@property (nonatomic, copy, readonly) UIAlertController * (^ addDesAction)(NSString *title, NSInteger index);
@property (nonatomic, copy, readonly) UIAlertController * (^ addCancelAction)(NSString *title, NSInteger index);
@property (nonatomic, copy, readonly) UIAlertController * (^ addDefaultAction)(NSString *title, NSInteger index);
@property (nonatomic, copy, readonly) UIAlertController * (^ addTextField) (void (^ textField) (UITextField *textField));
/**
在点语法中用来返回一个最近添加的UIAlertAction,用来设置样式
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ actionStyle) (void (^ actionStyle)(UIAlertAction * action));
/**
action点击方法,返回的key值是上面添加的key值
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ actionTap) (wtcAlertTapBlock tapIndex);
/**
在点语法中用来返回当前的UIAlertVController,用来设置样式
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ alertStyle) (void (^ alert)(UIAlertController *alertVC));
/**
title样式设置
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleAttributeFontWithColor)(UIFont *font, UIColor *color);
@property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleAttributeWidthDictionary)(void (^attribute)(NSMutableDictionary * attributes));
/**
message样式设置
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ alertMessageAttributeFontWithColor)(UIFont *font, UIColor *color);
@property (nonatomic, copy, readonly) UIAlertController * (^ alertMessageAttributeWidthDictionary)(void (^attribute)(NSMutableDictionary * attributes));
//detail
//@property (nonatomic, copy, readonly) UIAlertController * (^ alertDetailAttributeFontWithColor)(UIFont *font, UIColor *color);
//@property (nonatomic, copy, readonly) UIAlertController * (^ alertDetailAttributeWidthDictionary)(void (^attribute)(NSMutableDictionary * attributes));
/**
title属性
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleMaxNum)(NSUInteger numberOfLines);
@property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleLineBreakMode)(NSLineBreakMode mode);
/**
设置title字体颜色
*/
- (void)setACTitleAttributedString:(nullable NSAttributedString *)attributedString;
/**
设置message字体颜色
*/
- (void)setACMessageAttributedString:(nullable NSAttributedString *)attributedString;
/**
设置介绍字体颜色
*/
- (void)setACDetailAttributedString:(nullable NSAttributedString *)attributedString;
/**
设置title最大行数
*/
- (void)setACTitleLineMaxNumber:(NSInteger)number;
/**
设置title截断模式
*/
- (void)setACTitleLineBreakModel:(NSLineBreakMode)mode;
/**
添加action
*/
- (UIAlertAction *)addActionTitle:(NSString *)title style:(UIAlertActionStyle)style block:(void (^) (UIAlertAction *action))block;
@end
刚上来的3个函数方法用来便捷构造,接下来是一些readonly的block属性,是为了用来实现链式语法(在我们平常使用block是用来回调的,链式语法使用相反的思想,本类回调本类block并返回本类,借助block实现了链式语法)。还有一些通过kvo设置UIAlertController属性的一些方法。
在实现的时候需要解决几个问题:
1.如何统一action点击事件,并区分。
2.如何在action点击时直接获取当前AlertController
3.如何确定当前的presentingViewController
问题一:如何统一action点击事件,并区分。
解决方法如下:
- (UIAlertController * _Nonnull (^)(NSString * _Nonnull, UIAlertActionStyle, NSInteger))addAction{
return ^ (NSString *title, UIAlertActionStyle style, NSInteger index){
__weak typeof(self)weakSelf = self;
[self addActionTitle:title style:style block:^(UIAlertAction * _Nonnull action) {
if ([weakSelf wtc_actionBlock]) {
[weakSelf wtc_actionBlock](index, action);
}
}];
return self;
};
}
block拥有的特性是可以保存变量,所以在执行addAction这个操作时可以给action添加一个数值类型的值,在执行点击block回调点击的actionTap方法时回调这个值用来判断。
typedef void (^ wtcAlertTapBlock)(NSInteger index, UIAlertAction *action);
- (UIAlertController * _Nonnull (^)(wtcAlertTapBlock _Nonnull))actionTap{
return ^ (wtcAlertTapBlock block){
[self setWtc_actionBlock:block];
return self;
};
}
- (wtcAlertTapBlock)wtc_actionBlock{
return objc_getAssociatedObject(self, kwtcActionBlock);
}
- (void)setWtc_actionBlock:(wtcAlertTapBlock)block{
objc_setAssociatedObject(self, kwtcActionBlock, block,OBJC_ASSOCIATION_COPY);
}
关于点击的实现,可以定义一个block,返回action和设置时传入的标识(Index)来进行action的区分。用runtime动态绑定一个wtcAlertTapBlock类型的block,并在执行actionTap设置这个block。
- (UIAlertController * _Nonnull (^)(void (^ _Nonnull)(UIAlertAction * _Nonnull)))actionStyle{
return ^ (void (^style) (UIAlertAction *action)){
if (style) {
style([self wtc_currentAction]);
}
return self;
};
}
- (UIAlertAction *)wtc_currentAction{
return [self.actions lastObject];
}
通过UIAlertController的actions属性(能获取当前添加所有的action)来获取最新添加的action,并设置样式。
经过一系列的操作,我们就能够方便的设置action和处理回调了。
问题二:如何在action点击时直接获取当前AlertController
解决方法:
在分类中添加属性alertViewController
@interface UIAlertAction (WTCCategory)
@property (nonatomic, weak, readonly) UIAlertController * alertViewController;
/**
设置action颜色
*/
- (void)setAlertActionTitleColor:(UIColor *)color;
/**
设置action图片
*/
- (void)setAlertImage:(UIImage *)image;
@end
我们都知道分类不可以添加属性,只能通过runtime来实现,但是因为UIAlertController是持有action的,所以如果action强持有AlertViewController会造成循环引用,所以这里属性只能用weak,然而,
runtime绑定属性策略可以看出是没有weak这个属性的,解决这个问题有两个思路,
第一,用:OBJC_ASSOCIATION_ASSIGN,在UIAlertController的dealloc方法中将action中的alertViewController置nil,避免野指针,但是这样还需要用到runtime的方法交换,太麻烦了。
第二:设置一个中间者weak持有UIAlertController,action强持有中间者,这里我们就用的这种方法。
@interface UIAlertActionWithController : NSObject
@property (nonatomic, weak) UIAlertController * alertViewController;
@end
@implementation UIAlertActionWithController
@end
@implementation UIAlertAction (WTCCategory)
- (void)setAlertViewController:(UIAlertActionWithController *)model{
objc_setAssociatedObject(self, kWTCCategoryActionViewController, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIAlertActionWithController *)alertViewActionWithController{
return objc_getAssociatedObject(self, kWTCCategoryActionViewController);
}
- (UIAlertController *)alertViewController{
return [self alertViewActionWithController].alertViewController;
}
- (void)setAlertActionTitleColor:(UIColor *)color{
[self setValue:color forKey:@"_titleTextColor"];
}
- (void)setAlertImage:(UIImage *)image{
[self setValue:[image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forKey:@"image"];
}
@end
在添加Action的时候
- (UIAlertAction *)addActionTitle:(NSString *)title style:(UIAlertActionStyle)style block:(void (^)(UIAlertAction * _Nonnull))block{
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:style handler:block];
[self addAction:action];
UIAlertActionWithController *model = [UIAlertActionWithController new];
model.alertViewController = self;
[action setAlertViewController:model];
return action;
}
这样我们就实现了,从action中获取action的AlertViewController。
问题三:如何确定当前的presentingViewController
/**
立刻显示
*/
- (void)showInRootViewController;
//显示延时time小时
- (void)showAndDissmissAfterTime:(NSTimeInterval)time;
如果在某个model、view或者非ViewController中我们也想像UIAlertView一样方便,设置完成后只调用一个show方法就可以弹框该怎么办呢,那就只能从AppDelegate中获取当前显示的控制器了。
- (void)showInRootViewController{
UIViewController *vc = [UIApplication currentTopViewController];
[self presentedFromViewController:vc];
}
- (void)showAndDissmissAfterTime:(NSTimeInterval)time{
if (time > 0) {
UIViewController *vc = [UIApplication currentTopViewController];
[vc presentViewController:self animated:YES completion:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:NO completion:nil];
});
}];
}
}
UIApplication分类方法
+ (UIViewController *)currentTopViewController{
UIViewController *vc = [self rootViewController];
Class naVi = [UINavigationController class];
Class tabbarClass = [UITabBarController class];
BOOL isNavClass = [vc isKindOfClass:naVi];
BOOL isTabbarClass;
if (!isNavClass) {
isTabbarClass = [vc isKindOfClass:tabbarClass];
}
while (isNavClass || isTabbarClass) {
UIViewController * top;
if (isNavClass) {
top = [(UINavigationController *)vc topViewController];
}else{
top = [(UITabBarController *)vc selectedViewController];
}
if (top) {
vc = top;
}else{
break;
}
isNavClass = [vc isKindOfClass:naVi];
if (!isNavClass) {
isTabbarClass = [vc isKindOfClass:tabbarClass];
}
}
return vc;
}
+ (UIWindow *)delegateWindow{
return [UIApplication sharedApplication].delegate.window;
}
+ (id)rootViewController{
return [self delegateWindow].rootViewController;
}
此处需要注意的是:
Class naVi = [UINavigationController class];
Class tabbarClass = [UITabBarController class];
BOOL isNavClass = [vc isKindOfClass:naVi];
BOOL isTabbarClass;
if (!isNavClass) {
isTabbarClass = [vc isKindOfClass:tabbarClass];
}
while (isNavClass || isTabbarClass) {
UIViewController * top;
if (isNavClass) {
top = [(UINavigationController *)vc topViewController];
}else{
top = [(UITabBarController *)vc selectedViewController];
}
if (top) {
vc = top;
}else{
break;
}
isNavClass = [vc isKindOfClass:naVi];
if (!isNavClass) {
isTabbarClass = [vc isKindOfClass:tabbarClass];
}
}
这个递归,vc现获取到跟控制器,然后判断是否为NavgationController或TabbarController,如果是,则继续,否则,返回这个控制器。
总结
经过上面的一系列封装,我们就可以很方便的将UIAlertController封装的和UIAlertView一样简单,并添加更方便的功能。
网友评论