iOS 更加優雅便捷的UIAlertView/UIAlertController封裝使用

evcb1982 7年前發布 | 13K 次閱讀 iOS開發 移動開發 UIAlertController

前言:

這個是將alertView和alertController做了版本適配封裝在一起的,提供了變參和數組兩種方式,不過現在看來,雖然是“一句話”調用,但并不是很優雅的方式。

這次,改變了方案,將UIAlertView和UIAlertController分開進行了處理,整體代碼也輕量了很多。

  • 基于UIAlertView封裝的JXTAlertView,這個是將之前寫Demo時搞的一套快捷使用alertView的工具抽離整理出來的,并提供了C函數直接調用,像這樣:
    jxt_showAlertTitle(@"簡易調試使用alert,單按鈕,標題默認為“確定”");
    就可以直接顯示出一個alertView。
  • 基于UIAlertController封裝的JXTAlertController,支持iOS8及以上。調用方式為UIViewController的擴展分類方法,支持使用鏈式語法的方式配置alert的按鈕及樣式,相對于變參或者數組,更加簡潔。

JXTAlertManager

JXTAlertManager大體結構

1. JXTAlertView 便捷調試工具

之所以叫做快捷調試工具,就是因為這套代碼是之前寫Demo時搞出來的。所以,如果不是要適配iOS7及以下版本的話,這套代碼還是建議只用在平時Demo測試。也因此,并沒有針對UIActionSheet再進行封裝,說白了是因為懶……

平時寫一些Demo代碼時,總會用到alert、toast、HUD這些工具,如果沒有一套簡便的工具,會麻煩很多,所以便從輕量便捷角度出發,基于UIAlertView,封裝實現了alert、toast、HUD這些常用工具。

JXTAlertView大致效果演示

1.1.快捷使用alertView

如果只是簡單的一個提示,可以這樣使用(這里只是一個示例,詳細用法見源碼):

jxt_showAlertTitle(@"簡易調試使用alert,單按鈕,標題默認為“確定”");

其實現是基于:

[JXTAlertViewshowAlertViewWithTitle:title
                            message:message
                  cancelButtonTitle:cancelButtonTitle
                    otherButtonTitle:otherButtonTitle
                  cancelButtonBlock:cancelBlock
                    otherButtonBlock:otherBlock];

這是常用的兩個以內的按鈕的alertView,也可以這樣使用:

jxt_showAlertTwoButton(@"title", @"message", @"cancel", ^(NSIntegerbuttonIndex) {
    NSLog(@"cancel");
}, @"other", ^(NSIntegerbuttonIndex) {
    NSLog(@"other");
});

針對于復雜的多按鈕的alertView,還是使用變參方式,按鈕響應,根據添加的按鈕標題的index號依序區分:

[JXTAlertViewshowAlertViewWithTitle:@"title"
                            message:@"message"
                  cancelButtonTitle:@"cancel"
                    buttonIndexBlock:^(NSIntegerbuttonIndex) {
    if (buttonIndex == 0) {
        NSLog(@"cancel");
    }
    else if (buttonIndex == 1) {
        NSLog(@"按鈕1");
    }
    else if (buttonIndex == 2) {
        NSLog(@"按鈕2");
    }
    else if (buttonIndex == 3) {
        NSLog(@"按鈕3");
    }
    else if (buttonIndex == 4) {
        NSLog(@"按鈕4");
    }
    else if (buttonIndex == 5) {
        NSLog(@"按鈕5");
    }
} otherButtonTitles:@"按鈕1", @"按鈕2", @"按鈕3", @"按鈕4", @"按鈕5", nil];

1.2.簡單的toast

這里的toast提示,有別于傳統意義上的toast,因為其是基于alertView實現的,是一個沒有按鈕的alertView。可自定義展示延時時間,支持關閉回調的配置。

[JXTAlertViewshowToastViewWithTitle:@"title"
                            message:@"message"
                            duration:2
                  dismissCompletion:^(NSIntegerbuttonIndex) {
    NSLog(@"關閉");
}];

1.3.三種HUD的實現

這里的HUD區別于toast之處在于,其關閉時機可控,并不是單純的一個延時展示。

三種HUD是指單純的文字型、帶風火輪(菊花)的加載窗、帶進度條的加載窗。

后兩者用KVC的方式訪問了alertView的私有屬性 accessoryView 實現,這樣做可能沒有太大問題,不過還是不建議線上開發使用,而且利用這種方式訪問私有屬性本來就是不太安全的,一旦key(私有屬性名)改變了,不做容錯處理,會崩潰,源碼實現中做了一定的容錯,但是,一旦對應key變化,也就導致對應功能失效了。

  • 示例代碼(C函數方式):

    jxt_showLoadingHUDTitleMessage(@"title", @"message");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      jxt_dismissHUD();
    });
    

    HUD還有對應的簡易的顯示加載成功失敗狀態的方法,以及刷新進度條進度值的方法,詳情見Demo。

2. JXTAlertController(iOS8)(鏈式語法的“隱患”)

JXTAlertController是基于系統的UIAlertController封裝的,因此也只能支持iOS8及以上系統版本。

雖然源碼中的 JXTAlertManagerHeader.h 做了一個版本適配,但是,其意義更多在于提示,很可能因此出錯,所以,如果要適配iOS7,對應方法還是需要自行適配。

下面都以alert舉例,actionSheet同理。

JXTAlertController大致效果演示

2.1.結構說明

/**
JXTAlertController: show-alert(iOS8)
 
@param title             title
@param message           message
@param appearanceProcess alert配置過程
@param actionBlock       alert點擊響應回調
*/
- (void)jxt_showAlertWithTitle:(nullableNSString *)title
                      message:(nullableNSString *)message
            appearanceProcess:(JXTAlertAppearanceProcess)appearanceProcess
                  actionsBlock:(nullableJXTAlertActionBlock)actionBlockNS_AVAILABLE_IOS(8_0);

上述方法是針對 UIViewController 做的分類擴展,詳見源碼。

也就是在某個VC中使用時,可直接用 self 指針調用。

JXTAlertAppearanceProcess 是配置塊, JXTAlertActionBlock 是按鈕響應回調塊。

2.2.鏈式語法添加按鈕

[self jxt_showActionSheetWithTitle:@"title"
                          message:@"message"
                appearanceProcess:^(JXTAlertController * _NonnullalertMaker) {
    alertMaker.
    addActionCancelTitle(@"cancel").
    addActionDestructiveTitle(@"按鈕1").
    addActionDefaultTitle(@"按鈕2").
    addActionDefaultTitle(@"按鈕3").
    addActionDestructiveTitle(@"按鈕4");
} actionsBlock:^(NSIntegerbuttonIndex, UIAlertAction * _Nonnullaction, JXTAlertController * _NonnullalertSelf) {
 
    if ([action.titleisEqualToString:@"cancel"]) {
        NSLog(@"cancel");
    }
    else if ([action.titleisEqualToString:@"按鈕1"]) {
        NSLog(@"按鈕1");
    }
    else if ([action.titleisEqualToString:@"按鈕2"]) {
        NSLog(@"按鈕2");
    }
    else if ([action.titleisEqualToString:@"按鈕3"]) {
        NSLog(@"按鈕3");
    }
    else if ([action.titleisEqualToString:@"按鈕4"]) {
        NSLog(@"按鈕4");
    }
}];

appearanceProcess 配置塊中, alertMaker 是當前alertController對象, addActionCancelTitle(@"cancel") 是添加一個按鈕,其等效于:

[alertControlleraddAction:[UIAlertActionactionWithTitle:@"cancel" style:UIAlertActionStyleCancelhandler:^(UIAlertAction * _Nonnullaction) {
 
}]];

這里引入了簡單的鏈式語法,可以連續添加系統支持的三類action按鈕,當然 UIAlertActionStyleCancel 這個樣式的action只能添加一次。這樣可以大大簡化代碼。

actionsBlock 是action按鈕響應回調,可以根據index區分響應,index根據執行add的語法鏈從0依序增加,cancel類型的action布局位置是固定的,和添加順序無關,但其index與添加順序有關。

對于復雜或者特殊的alertController,也可以根據action.title或者action區分響應。

2.3.鏈式語法的“隱患”

用過 Masonry 這個庫的,應該都對鏈式語法不會太陌生。鏈式語法可以使得代碼簡化且邏輯清晰化。但是,其也有一定的“隱患”存在。

Masonry 應該是用的最多的一個自動布局的三方庫,類似的還有 SDAutoLayout (這里只是舉例,同樣的三方還有很多,這個應該是除了 Masonry 外,用的相對多一些的一個)這樣的,同樣的鏈式語法,后者似乎更加簡潔優雅。那為什么大名鼎鼎的 Masonry 不這么干呢?我想是因為“安全”。

用 SDAutoLayout 的Demo做一個實驗:

view為nil導致的崩潰

這里把view0置為nil,之后運行,程序直接崩潰了。。。這類似于執行一個未賦值的空block。

有人可能會認為這樣的實驗沒有意義,為nil了干嘛還要布局呢?其實這是筆者前陣子在封裝一個鏈式庫時遇到的問題:實際應用開發中,情況要復雜很多,有些view是動態添加的,甚至是根據接口數據動態創建的,如果在處理這類代碼邏輯中稍有不慎,就會造成上述問題,給不存在的view進行布局,直接導致程序崩潰。。。

其實這也是代碼書寫規范的問題,針對這類動態view,在處理時,本就應該添加 if 條件判斷的,不過有時容易忽視,或者他人接手相關代碼時,也容易忽略。如果用 Masonry 的塊配置布局,就不會發生這類問題,因為這種情況,對于 Masonry 那種寫法,就是一個空指針執行一個方法,其結果就是不執行,而像 SDAutoLayout 這類的,不作判空處理,就會導致程序崩潰。這里尤其要注意。

2.4.其他配置

針對alert上的輸入框,保持系統的添加方式,示例如下:

[self jxt_showAlertWithTitle:@"title"
                    message:@"message"
          appearanceProcess:^(JXTAlertController * _NonnullalertMaker) {
    alertMaker.
    addActionDestructiveTitle(@"獲取輸入框1").
    addActionDestructiveTitle(@"獲取輸入框2");
 
    [alertMakersetAlertDidShown:^{
        [self logMsg:@"alertDidShown"];//不用擔心循環引用
    }];
    alertMaker.alertDidDismiss = ^{
        [self logMsg:@"alertDidDismiss"];
    };
 
    [alertMakeraddTextFieldWithConfigurationHandler:^(UITextField * _NonnulltextField) {
        textField.placeholder = @"輸入框1-請輸入";
    }];
    [alertMakeraddTextFieldWithConfigurationHandler:^(UITextField * _NonnulltextField) {
        textField.placeholder = @"輸入框2-請輸入";
    }];
} actionsBlock:^(NSIntegerbuttonIndex, UIAlertAction * _Nonnullaction, JXTAlertController * _NonnullalertSelf) {
    if (buttonIndex == 0) {
        UITextField *textField = alertSelf.textFields.firstObject;
        [self logMsg:textField.text];//不用擔心循環引用
    }
    else if (buttonIndex == 1) {
        UITextField *textField = alertSelf.textFields.lastObject;
        [self logMsg:textField.text];
    }
}];

對于alert展示和關閉的回調,同樣支持以block的方式配置。

如果 appearanceProcess 塊不進行任何配置操作,即無按鈕的alert,同樣默認以toast模式處理。可通過設置 toastStyleDuration 屬性,配置toast展示延遲時間。

2.5.改變alertController的字體顏色

可以通過KVC的方式訪問alertController的私有屬性,從而進行修改對應的字體的顏色,甚至字體。

對于 UIAlertAction ,可以用下面的方式修改字體顏色:

[alertAction setValue:[UIColor grayColor] forKey:@"titleTextColor"];

修改UIAlertAction字體顏色的效果

但是就像前面說的,個人并不推薦這類方式,所以源碼中沒有提供相關的方法。

如果有對應的特殊需求,總體來說,還是自定義alert視圖比較靈活,網上相關的開源庫也有很多可以直接使用,不必總是糾結于系統的實現方式。

最后,歡迎使用JXTAlertManager,如果遇到任何問題,請及時聯系作者。

 

 

 本文由用戶 evcb1982 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!