UIAlertView在iOS9后就不建議被使用阳柔,官方要求最好使用UIAlertViewController松申,它集成了UIAlertView和UIActionSheet撮奏。UIAlertView和UIActionSheet是建立在View的基礎(chǔ)上隅要,調(diào)用show方法可以直接顯示报账,并通過代理來實(shí)現(xiàn)點(diǎn)擊方法的回調(diào)研底,而UIAlertController是建立在UIViewController之上的,需要present顯示透罢,并且所有的點(diǎn)擊按鈕都通過UIAction的model進(jìn)行創(chuàng)建榜晦,當(dāng)然兩者還有很多區(qū)別的。今天羽圃,我們關(guān)注的重點(diǎn)是在項(xiàng)目中有大量使用的UIAlertView和UIActionSheet如何被替換為UIAlertViewController乾胶,如果按正常的邏輯:
而UIAlertView要先遵循代理:
可以看出,如果在項(xiàng)目中有需要將UIAlertView中替換成UIAlertController還是需要做很多工作的朽寞。今天识窿,我們來簡(jiǎn)化一下這個(gè)工作,需要寫一個(gè)UIAlertController的分類脑融,效果是這樣:
可以看到喻频,封裝后的可以很方便的兼容UIAlertView的代理方法,而且只需要一行代碼肘迎。
接下來看封裝的頭文件
/**
快速創(chuàng)建
*/
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甥温,并設(shè)置key值,需要在點(diǎn)擊方法中使用
*/
@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));
/**
在點(diǎn)語(yǔ)法中用來返回一個(gè)最近添加的UIAlertAction妓布,用來設(shè)置樣式
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ actionStyle) (void (^ actionStyle)(UIAlertAction * action));
/**
action點(diǎn)擊方法姻蚓,返回的key值是上面添加的key值
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ actionTap) (wtcAlertTapBlock tapIndex);
/**
在點(diǎn)語(yǔ)法中用來返回當(dāng)前的UIAlertVController,用來設(shè)置樣式
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ alertStyle) (void (^ alert)(UIAlertController *alertVC));
/**
title樣式設(shè)置
*/
@property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleAttributeFontWithColor)(UIFont *font, UIColor *color);
@property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleAttributeWidthDictionary)(void (^attribute)(NSMutableDictionary * attributes));
/**
message樣式設(shè)置
*/
@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);
/**
設(shè)置title字體顏色
*/
- (void)setACTitleAttributedString:(nullable NSAttributedString *)attributedString;
/**
設(shè)置message字體顏色
*/
- (void)setACMessageAttributedString:(nullable NSAttributedString *)attributedString;
/**
設(shè)置介紹字體顏色
*/
- (void)setACDetailAttributedString:(nullable NSAttributedString *)attributedString;
/**
設(shè)置title最大行數(shù)
*/
- (void)setACTitleLineMaxNumber:(NSInteger)number;
/**
設(shè)置title截?cái)嗄J? */
- (void)setACTitleLineBreakModel:(NSLineBreakMode)mode;
/**
添加action
*/
- (UIAlertAction *)addActionTitle:(NSString *)title style:(UIAlertActionStyle)style block:(void (^) (UIAlertAction *action))block;
@end
剛上來的3個(gè)函數(shù)方法用來便捷構(gòu)造匣沼,接下來是一些readonly的block屬性狰挡,是為了用來實(shí)現(xiàn)鏈?zhǔn)秸Z(yǔ)法(在我們平常使用block是用來回調(diào)的,鏈?zhǔn)秸Z(yǔ)法使用相反的思想释涛,本類回調(diào)本類block并返回本類加叁,借助block實(shí)現(xiàn)了鏈?zhǔn)秸Z(yǔ)法)。還有一些通過kvo設(shè)置UIAlertController屬性的一些方法枢贿。
在實(shí)現(xiàn)的時(shí)候需要解決幾個(gè)問題:
1.如何統(tǒng)一action點(diǎn)擊事件殉农,并區(qū)分。
2.如何在action點(diǎn)擊時(shí)直接獲取當(dāng)前AlertController
3.如何確定當(dāng)前的presentingViewController
問題一:如何統(tǒng)一action點(diǎn)擊事件局荚,并區(qū)分超凳。
解決方法如下:
- (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擁有的特性是可以保存變量,所以在執(zhí)行addAction這個(gè)操作時(shí)可以給action添加一個(gè)數(shù)值類型的值耀态,在執(zhí)行點(diǎn)擊block回調(diào)點(diǎn)擊的actionTap方法時(shí)回調(diào)這個(gè)值用來判斷轮傍。
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);
}
關(guān)于點(diǎn)擊的實(shí)現(xiàn),可以定義一個(gè)block首装,返回action和設(shè)置時(shí)傳入的標(biāo)識(shí)(Index)來進(jìn)行action的區(qū)分创夜。用runtime動(dòng)態(tài)綁定一個(gè)wtcAlertTapBlock類型的block,并在執(zhí)行actionTap設(shè)置這個(gè)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屬性(能獲取當(dāng)前添加所有的action)來獲取最新添加的action驰吓,并設(shè)置樣式涧尿。
經(jīng)過一系列的操作,我們就能夠方便的設(shè)置action和處理回調(diào)了檬贰。
問題二:如何在action點(diǎn)擊時(shí)直接獲取當(dāng)前AlertController
解決方法:
在分類中添加屬性alertViewController
@interface UIAlertAction (WTCCategory)
@property (nonatomic, weak, readonly) UIAlertController * alertViewController;
/**
設(shè)置action顏色
*/
- (void)setAlertActionTitleColor:(UIColor *)color;
/**
設(shè)置action圖片
*/
- (void)setAlertImage:(UIImage *)image;
@end
我們都知道分類不可以添加屬性姑廉,只能通過runtime來實(shí)現(xiàn),但是因?yàn)閁IAlertController是持有action的翁涤,所以如果action強(qiáng)持有AlertViewController會(huì)造成循環(huán)引用桥言,所以這里屬性只能用weak,然而葵礼,
可以看出是沒有weak這個(gè)屬性的号阿,解決這個(gè)問題有兩個(gè)思路,
第一鸳粉,用:OBJC_ASSOCIATION_ASSIGN扔涧,在UIAlertController的dealloc方法中將action中的alertViewController置nil,避免野指針赁严,但是這樣還需要用到runtime的方法交換扰柠,太麻煩了。
第二:設(shè)置一個(gè)中間者weak持有UIAlertController,action強(qiáng)持有中間者疼约,這里我們就用的這種方法卤档。
@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的時(shí)候
- (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;
}
這樣我們就實(shí)現(xiàn)了,從action中獲取action的AlertViewController程剥。
問題三:如何確定當(dāng)前的presentingViewController
/**
立刻顯示
*/
- (void)showInRootViewController;
//顯示延時(shí)time小時(shí)
- (void)showAndDissmissAfterTime:(NSTimeInterval)time;
如果在某個(gè)model劝枣、view或者非ViewController中我們也想像UIAlertView一樣方便,設(shè)置完成后只調(diào)用一個(gè)show方法就可以彈框該怎么辦呢,那就只能從AppDelegate中獲取當(dāng)前顯示的控制器了织鲸。
- (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];
}
}
這個(gè)遞歸舔腾,vc現(xiàn)獲取到跟控制器,然后判斷是否為NavgationController或TabbarController搂擦,如果是稳诚,則繼續(xù),否則瀑踢,返回這個(gè)控制器扳还。
總結(jié)
經(jīng)過上面的一系列封裝,我們就可以很方便的將UIAlertController封裝的和UIAlertView一樣簡(jiǎn)單橱夭,并添加更方便的功能氨距。