鏈?zhǔn)秸Z(yǔ)法封裝一個(gè)簡(jiǎn)單的UIAlertController

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乾胶,如果按正常的邏輯:

UIAlertController創(chuàng)建和顯示

而UIAlertView要先遵循代理:

UIAlertView創(chuàng)建和顯示

可以看出,如果在項(xiàng)目中有需要將UIAlertView中替換成UIAlertController還是需要做很多工作的朽寞。今天识窿,我們來簡(jiǎn)化一下這個(gè)工作,需要寫一個(gè)UIAlertController的分類脑融,效果是這樣:


封裝后的UIAlertViewController

可以看到喻频,封裝后的可以很方便的兼容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,然而葵礼,

runtime綁定屬性策略

可以看出是沒有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)單橱夭,并添加更方便的功能氨距。

github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市棘劣,隨后出現(xiàn)的幾起案子俏让,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件首昔,死亡現(xiàn)場(chǎng)離奇詭異寡喝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)勒奇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門拘荡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撬陵,你說我怎么就攤上這事⊥欤” “怎么了巨税?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)粉臊。 經(jīng)常有香客問我草添,道長(zhǎng),這世上最難降的妖魔是什么扼仲? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任远寸,我火速辦了婚禮,結(jié)果婚禮上屠凶,老公的妹妹穿的比我還像新娘驰后。我一直安慰自己,他們只是感情好矗愧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布灶芝。 她就那樣靜靜地躺著,像睡著了一般唉韭。 火紅的嫁衣襯著肌膚如雪夜涕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天属愤,我揣著相機(jī)與錄音女器,去河邊找鬼。 笑死住诸,一個(gè)胖子當(dāng)著我的面吹牛驾胆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播只壳,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼俏拱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了吼句?” 一聲冷哼從身側(cè)響起锅必,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后搞隐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驹愚,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年劣纲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逢捺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡癞季,死狀恐怖劫瞳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绷柒,我是刑警寧澤志于,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站废睦,受9級(jí)特大地震影響伺绽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嗜湃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一奈应、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧购披,春花似錦杖挣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至橘荠,卻和暖如春屿附,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哥童。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工挺份, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贮懈。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓匀泊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親朵你。 傳聞我的和親對(duì)象是個(gè)殘疾皇子各聘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349