原文
前言:
之前做過一套關於UIAlertView/UIAlertController的混合封裝,詳見:
iOS (封裝)一句話調用系統的alertView和alertController
這個是將alertView和alertController做了版本適配封裝在一起的,提供了變參和數組兩種方式,不過現在看來,雖然是“一句話”調用,但並不是很優雅的方式。
這次,改變了方案,將UIAlertView和UIAlertController分開進行了處理,整體代碼也輕量了很多。
基於UIAlertView封裝的JXTAlertView,這個是將之前寫Demo時搞的一套快捷使用alertView的工具抽離整理出來的,並提供了C函數直接調用,像這樣:
jxt_showAlertTitle(@"簡易調試使用alert,單按鈕,標題默認為“確定”");
就可以直接顯示出一個alertView。
基於UIAlertController封裝的JXTAlertController,支持iOS8及以上。調用方式為UIViewController的擴展分類方法,支持使用鏈式語法的方式配置alert的按鈕及樣式,相對於變參或者數組,更加簡潔。
代碼及Demo見GitHub:
JXTAlertManager
JXTAlertManager大體結構
1. JXTAlertView 便捷調試工具
之所以叫做快捷調試工具,就是因為這套代碼是之前寫Demo時搞出來的。所以,如果不是要適配iOS7及以下版本的話,這套代碼還是建議只用在平時Demo測試。也因此,並沒有針對UIActionSheet再進行封裝,說白了是因為懶……
平時寫一些Demo代碼時,總會用到alert、toast、HUD這些工具,如果沒有一套簡便的工具,會麻煩很多,所以便從輕量便捷角度出發,基於UIAlertView,封裝實現了alert、toast、HUD這些常用工具。
JXTAlertView大致效果演示
1.1.快捷使用alertView
如果只是簡單的一個提示,可以這樣使用(這裡只是一個示例,詳細用法見源碼):
jxt_showAlertTitle(@"簡易調試使用alert,單按鈕,標題默認為“確定”");
其實現是基於:
[JXTAlertView showAlertViewWithTitle:title message:message cancelButtonTitle:cancelButtonTitle otherButtonTitle:otherButtonTitle cancelButtonBlock:cancelBlock otherButtonBlock:otherBlock];
這是常用的兩個以內的按鈕的alertView,也可以這樣使用:
jxt_showAlertTwoButton(@"title", @"message", @"cancel", ^(NSInteger buttonIndex) { NSLog(@"cancel"); }, @"other", ^(NSInteger buttonIndex) { NSLog(@"other"); });
針對於復雜的多按鈕的alertView,還是使用變參方式,按鈕響應,根據添加的按鈕標題的index號依序區分:
[JXTAlertView showAlertViewWithTitle:@"title" message:@"message" cancelButtonTitle:@"cancel" buttonIndexBlock:^(NSInteger buttonIndex) { 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。可自定義展示延時時間,支持關閉回調的配置。
[JXTAlertView showToastViewWithTitle:@"title" message:@"message" duration:2 dismissCompletion:^(NSInteger buttonIndex) { 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:(nullable NSString *)title message:(nullable NSString *)message appearanceProcess:(JXTAlertAppearanceProcess)appearanceProcess actionsBlock:(nullable JXTAlertActionBlock)actionBlock NS_AVAILABLE_IOS(8_0);
上述方法是針對UIViewController做的分類擴展,詳見源碼。
也就是在某個VC中使用時,可直接用self指針調用。
JXTAlertAppearanceProcess是配置塊,JXTAlertActionBlock是按鈕響應回調塊。
2.2.鏈式語法添加按鈕
[self jxt_showActionSheetWithTitle:@"title" message:@"message" appearanceProcess:^(JXTAlertController * _Nonnull alertMaker) { alertMaker. addActionCancelTitle(@"cancel"). addActionDestructiveTitle(@"按鈕1"). addActionDefaultTitle(@"按鈕2"). addActionDefaultTitle(@"按鈕3"). addActionDestructiveTitle(@"按鈕4"); } actionsBlock:^(NSInteger buttonIndex, UIAlertAction * _Nonnull action, JXTAlertController * _Nonnull alertSelf) { if ([action.title isEqualToString:@"cancel"]) { NSLog(@"cancel"); } else if ([action.title isEqualToString:@"按鈕1"]) { NSLog(@"按鈕1"); } else if ([action.title isEqualToString:@"按鈕2"]) { NSLog(@"按鈕2"); } else if ([action.title isEqualToString:@"按鈕3"]) { NSLog(@"按鈕3"); } else if ([action.title isEqualToString:@"按鈕4"]) { NSLog(@"按鈕4"); } }];
appearanceProcess配置塊中,alertMaker是當前alertController對象,addActionCancelTitle(@"cancel")是添加一個按鈕,其等效於:
[alertController addAction:[UIAlertAction actionWithTitle:@"cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]];
這裡引入了簡單的鏈式語法,可以連續添加系統支持的三類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 * _Nonnull alertMaker) { alertMaker. addActionDestructiveTitle(@"獲取輸入框1"). addActionDestructiveTitle(@"獲取輸入框2"); [alertMaker setAlertDidShown:^{ [self logMsg:@"alertDidShown"];//不用擔心循環引用 }]; alertMaker.alertDidDismiss = ^{ [self logMsg:@"alertDidDismiss"]; }; [alertMaker addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { textField.placeholder = @"輸入框1-請輸入"; }]; [alertMaker addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { textField.placeholder = @"輸入框2-請輸入"; }]; } actionsBlock:^(NSInteger buttonIndex, UIAlertAction * _Nonnull action, JXTAlertController * _Nonnull alertSelf) { 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,如果遇到任何問題,請及時聯系作者。
參考文章:
iOS更改UIActionController彈出字體的樣式
UIAlertController 簡單修改title以及按鈕的字體顏色
How to add subview inside UIAlertView for iOS 7?
UIAlertView addSubview in iOS7
iOS UIAlertView中UIActivityindicatorView風火輪提示加載等待