前言:
之前做過一套關於UIAlertView/UIAlertController的混合封裝,詳見:
這個是將alertView和alertController做了版本適配封裝在一起的,提供了變參和陣列兩種方式,不過現在看來,雖然是“一句話”呼叫,但並不是很優雅的方式。
這次,改變了方案,將UIAlertView和UIAlertController分開進行了處理,整體程式碼也輕量了很多。
- 基於UIAlertView封裝的JXTAlertView,這個是將之前寫Demo時搞的一套快捷使用alertView的工具抽離整理出來的,並提供了C函式直接呼叫,像這樣:
jxt_showAlertTitle(@"簡易除錯使用alert,單按鈕,標題預設為“確定”");
就可以直接顯示出一個alertView。 - 基於UIAlertController封裝的JXTAlertController,支援iOS8及以上。呼叫方式為UIViewController的擴充套件分類方法,支援使用鏈式語法的方式配置alert的按鈕及樣式,相對於變參或者陣列,更加簡潔。
程式碼及Demo見GitHub:
1. JXTAlertView 便捷除錯工具
之所以叫做快捷除錯工具,就是因為這套程式碼是之前寫Demo時搞出來的。所以,如果不是要適配iOS7及以下版本的話,這套程式碼還是建議只用在平時Demo測試。也因此,並沒有針對UIActionSheet再進行封裝,說白了是因為懶……
平時寫一些Demo程式碼時,總會用到alert、toast、HUD這些工具,如果沒有一套簡便的工具,會麻煩很多,所以便從輕量便捷角度出發,基於UIAlertView,封裝實現了alert、toast、HUD這些常用工具。
1.1.快捷使用alertView
如果只是簡單的一個提示,可以這樣使用(這裡只是一個示例,詳細用法見原始碼):
jxt_showAlertTitle(@"簡易除錯使用alert,單按鈕,標題預設為“確定”");
其實現是基於:
1 2 3 4 5 6 |
[JXTAlertView showAlertViewWithTitle:title message:message cancelButtonTitle:cancelButtonTitle otherButtonTitle:otherButtonTitle cancelButtonBlock:cancelBlock otherButtonBlock:otherBlock]; |
這是常用的兩個以內的按鈕的alertView,也可以這樣使用:
1 2 3 4 5 |
jxt_showAlertTwoButton(@"title", @"message", @"cancel", ^(NSInteger buttonIndex) { NSLog(@"cancel"); }, @"other", ^(NSInteger buttonIndex) { NSLog(@"other"); }); |
針對於複雜的多按鈕的alertView,還是使用變參方式,按鈕響應,根據新增的按鈕標題的index號依序區分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[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。可自定義展示延時時間,支援關閉回撥的配置。
1 2 3 4 5 6 |
[JXTAlertView showToastViewWithTitle:@"title" message:@"message" duration:2 dismissCompletion:^(NSInteger buttonIndex) { NSLog(@"關閉"); }]; |
1.3.三種HUD的實現
這裡的HUD區別於toast之處在於,其關閉時機可控,並不是單純的一個延時展示。
三種HUD是指單純的文字型、帶風火輪(菊花)的載入窗、帶進度條的載入窗。
後兩者用KVC的方式訪問了alertView的私有屬性accessoryView
實現,這樣做可能沒有太大問題,不過還是不建議線上開發使用,而且利用這種方式訪問私有屬性本來就是不太安全的,一旦key(私有屬性名)改變了,不做容錯處理,會崩潰,原始碼實現中做了一定的容錯,但是,一旦對應key變化,也就導致對應功能失效了。
- 示例程式碼(C函式方式):
1234jxt_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同理。
2.1.結構說明
1 2 3 4 5 6 7 8 9 10 11 12 |
/** 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.鏈式語法新增按鈕
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
[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")
是新增一個按鈕,其等效於:
1 2 3 |
[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做一個實驗:
這裡把view0置為nil,之後執行,程式直接崩潰了。。。這類似於執行一個未賦值的空block。
有人可能會認為這樣的實驗沒有意義,為nil了幹嘛還要佈局呢?其實這是筆者前陣子在封裝一個鏈式庫時遇到的問題:實際應用開發中,情況要複雜很多,有些view是動態新增的,甚至是根據介面資料動態建立的,如果在處理這類程式碼邏輯中稍有不慎,就會造成上述問題,給不存在的view進行佈局,直接導致程式崩潰。。。
其實這也是程式碼書寫規範的問題,針對這類動態view,在處理時,本就應該新增if
條件判斷的,不過有時容易忽視,或者他人接手相關程式碼時,也容易忽略。如果用Masonry
的塊配置佈局,就不會發生這類問題,因為這種情況,對於Masonry
那種寫法,就是一個空指標執行一個方法,其結果就是不執行,而像SDAutoLayout
這類的,不作判空處理,就會導致程式崩潰。這裡尤其要注意。
2.4.其他配置
針對alert上的輸入框,保持系統的新增方式,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
[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"];
但是就像前面說的,個人並不推薦這類方式,所以原始碼中沒有提供相關的方法。
如果有對應的特殊需求,總體來說,還是自定義alert檢視比較靈活,網上相關的開源庫也有很多可以直接使用,不必總是糾結於系統的實現方式。
最後,歡迎使用JXTAlertManager,如果遇到任何問題,請及時聯絡作者。
參考文章:
1.iOS更改UIActionController彈出字型的樣式
2.UIAlertController 簡單修改title以及按鈕的字型顏色
3.How to add subview inside UIAlertView for iOS 7?
4.UIAlertView addSubview in iOS7
5.iOS UIAlertView中UIActivityindicatorView風火輪提示載入等待