iOS 更加優雅便捷的UIAlertView/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,如果遇到任何問題,請及時聯系作者。