轉載 http://www.cocoachina.com/ios/20141126/10320.html
如果侵權,聯系刪除。
iOS 8的新特性之一就是讓接口更有適應性、更靈活,因此許多視圖控制器的實現方式發生了巨大的變化。全新的UIPresentationController在實現視圖控制器間的過渡動畫效果和自適應設備尺寸變化效果(比如說旋轉)中發揮了重要的作用,它有效地節省了程序員們的工作量(天地良心啊)。還有,某些舊的UIKit控件也同樣發生了許多變化,比如說Alert Views、Action Sheets、Popovers以及Search Bar Controllers。本文將會對Alert Views和Action Sheets發生的改變進行一個大致的介紹,我們會采用Objective-C和swift兩種語言同時進行代碼說明。
UIAlertView
隨著蘋果上次iOS 5的發布,對話框視圖樣式出現在了我們面前,直到現在它都沒有發生過很大的變化。下面的代碼片段展示了如何初始化和顯示一個帶有“取消”和“好的”按鈕的對話框視圖。
Objective-C版本:
UIAlertView *alertview = [[UIAlertView alloc] initWithTitle:@"標題"message:@"這個是UIAlertView的默認樣式"delegate:self cancelButtonTitle:@"取消"otherButtonTitles:@"好的", nil];
[alertview show];
UIAlertView的默認樣式
swift版本和Objective-C版本不同,在swift中,alertView的初始化只允許創建擁有一個取消按鈕的對話框視圖?;蛟S您可以看到帶有otherButtonTitles的init方法,但是很遺憾,這個方法是沒有辦法通過編譯的。
varalertView = UIAlertView(title:"標題", message:"這個是UIAlertView的默認樣式", delegate: self, cancelButtonTitle:"取消")
alertView.show()
swift版本的UIAlertView
要能夠創建和上面Objective-C版本相同的對話框視圖,我們可以采取曲線救國的方法,雖然麻煩了些,但是我們為了目的可以不擇手段的,是吧?
varalertView = UIAlertView()
alertView.delegate = self
alertView.title ="標題"
alertView.message ="這個是UIAlertView的默認樣式"
alertView.addButtonWithTitle("取消")
alertView.addButtonWithTitle("好的")
alertView.show()
您也可以通過更改UIAlertView的alertViewStyle屬性來實現輸入文字、密碼甚至登錄框的效果。
UIAlertView文本對話框
UIAlertView密碼對話框
UIAlertView登錄對話框
UIAlertViewDelegate協議擁有響應對話框視圖的按鈕動作的回調方法。還有當文本框內容改變時,調用alertViewShouldEnableOtherButton:方法可以讓按鈕動態地可用或者不可用。
要說明一點,蘋果官方現在并不提倡在iOS 8中使用UIAlertView,取而代之的是UIAlertController。下面我們就來介紹UIAlertController的使用方法。
UIAlertController
在iOS 8中,UIAlertController在功能上是和UIAlertView以及UIActionSheet相同的,UIAlertController以一種模塊化替換的方式來代替這兩貨的功能和作用。是使用對話框(alert)還是使用上拉菜單(action sheet),就取決于在創建控制器時,您是如何設置首選樣式的。
一個簡單的對話框例子
您可以比較一下兩種不同的創建對話框的代碼,創建基礎UIAlertController的代碼和創建UIAlertView的代碼非常相似:
Objective-C版本:
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"標題"message:@"這個是UIAlertController的默認樣式"preferredStyle:UIAlertControllerStyleAlert];
swift版本:
varalertController = UIAlertController(title:"標題", message:"這個是UIAlertController的默認樣式", preferredStyle: UIAlertControllerStyle.Alert)
同創建UIAlertView相比,我們無需指定代理,也無需在初始化過程中指定按鈕。不過要特別注意第三個參數,要確定您選擇的是對話框樣式還是上拉菜單樣式。
通過創建UIAlertAction的實例,您可以將動作按鈕添加到控制器上。UIAlertAction由標題字符串、樣式以及當用戶選中該動作時運行的代碼塊組成。通過UIAlertActionStyle,您可以選擇如下三種動作樣式:常規(default)、取消(cancel)以及警示(destruective)。為了實現原來我們在創建UIAlertView時創建的按鈕效果,我們只需創建這兩個動作按鈕并將它們添加到控制器上即可。
Objective-C版本:
UIAlertActioncancelAction = [UIAlertActionactionWithTitle:@"取消"style:UIAlertActionStyleCancelhandler:^(UIAlertAction_Nonnullaction) {
NSLog(@"取消了點擊");
}];
UIAlertActionokAction = [UIAlertActionactionWithTitle:@"好的"style:UIAlertActionStyleDefaulthandler:^(UIAlertAction_Nonnullaction) {
NSLog(@"點擊了確定");
}];
[alertController addAction:cancelAction];
[alertController addAction:okAction];
swift版本:
varcancelAction = UIAlertAction(title:"取消", style: UIAlertActionStyle.Cancel, handler: nil)
varokAction = UIAlertAction(title:"好的", style: UIAlertActionStyle.Default, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(okAction)
最后,我們只需顯示這個對話框視圖控制器即可:
Objective-C版本:
[self presentViewController:alertController animated:YES completion:nil];
swift版本:
self.presentViewController(alertController, animated:true, completion: nil)
UIAlertController默認樣式
按鈕顯示的次序取決于它們添加到對話框控制器上的次序。一般來說,根據蘋果官方制定的《iOS 用戶界面指南》,在擁有兩個按鈕的對話框中,您應當將取消按鈕放在左邊。要注意,取消按鈕是唯一的,如果您添加了第二個取消按鈕,那么你就會得到如下的一個運行時異常:
- Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘UIAlertController can only have one action with a style of UIAlertActionStyleCancel’
異常信息簡潔明了,我們在此就不贅述了。
“警示”樣式
什么是“警示”樣式呢?我們先不著急回答這個問題,先來看一下下面關于“警示”樣式的簡單示例。在這個示例中,我們將前面的示例中的“好的”按鈕替換為了“重置”按鈕。
Objective-C版本:
UIAlertAction *resetAction = [UIAlertAction actionWithTitle:@"重置"style:UIAlertActionStyleDestructive handler:nil];
[alertController addAction:resetAction];
swift版本:
varresetAction = UIAlertAction(title:"重置", style: UIAlertActionStyle.Destructive, handler: nil)
alertController.addAction(resetAction)
“警示”樣式
可以看出,我們新增的那個“重置”按鈕變成了紅色。根據蘋果官方的定義,“警示”樣式的按鈕是用在可能會改變或刪除數據的操作上。因此用了紅色的醒目標識來警示用戶。
文本對話框
UIAlertController極大的靈活性意味著您不必拘泥于內置樣式。以前我們只能在默認視圖、文本框視圖、密碼框視圖、登錄和密碼輸入框視圖中選擇,現在我們可以向對話框中添加任意數目的UITextField對象,并且可以使用所有的UITextField特性。當您向對話框控制器中添加文本框時,您需要指定一個用來配置文本框的代碼塊。
舉個栗子吧,要重新建立原來的登錄和密碼樣式對話框,我們可以向其中添加兩個文本框,然后用合適的占位符來配置它們,最后將密碼輸入框設置使用安全文本輸入。
Objective-C版本:
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"文本對話框"message:@"登錄和密碼對話框示例"preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
textField.placeholder = @"登錄";
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = @"密碼";
textField.secureTextEntry = YES;
}];
swift版本:
alertController.addTextFieldWithConfigurationHandler {
(textField: UITextField!) -> Voidin
textField.placeholder ="登錄"
}
alertController.addTextFieldWithConfigurationHandler {
(textField: UITextField!) -> Voidin
textField.placeholder ="密碼"
textField.secureTextEntry =true
}
在“好的”按鈕按下時,我們讓程序讀取文本框中的值。
Objective-C版本:
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的"style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
UITextField *login = alertController.textFields.firstObject;
UITextField *password = alertController.textFields.lastObject;
...
}];
swift版本:
varokAction = UIAlertAction(title:"好的", style: UIAlertActionStyle.Default) {
(action: UIAlertAction!) -> Voidin
varlogin = alertController.textFields?.first as UITextField
varpassword = alertController.textFields?.last as UITextField
}
如果我們想要實現UIAlertView中的委托方法alertViewShouldEnableOtherButton:方法的話可能會有一些復雜。假定我們要讓“登錄”文本框中至少有3個字符才能激活“好的”按鈕。很遺憾的是,在UIAlertController中并沒有相應的委托方法,因此我們需要向“登錄”文本框中添加一個Observer。Observer模式定義對象間的一對多的依賴關系,當一個對象的狀態發生改變時, 所有依賴于它的對象都得到通知并被自動更新。我們可以在構造代碼塊中添加如下的代碼片段來實現。
Objective-C版本:
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(alertTextFieldDidChange:) name:UITextFieldTextDidChangeNotification object:textField];
}];
swift版本:
alertController.addTextFieldWithConfigurationHandler {
(textField: UITextField!) -> Voidin
...
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("alertTextFieldDidChange:"), name: UITextFieldTextDidChangeNotification, object: textField)
}
當視圖控制器釋放的時候我們需要移除這個Observer,我們通過在每個按鈕動作的handler代碼塊(還有其他任何可能釋放視圖控制器的地方)中添加合適的代碼來實現它。比如說在okAction這個按鈕動作中:
Objective-C版本:
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的"style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
...
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
}];
swift版本:
varokAction = UIAlertAction(title:"好的", style: UIAlertActionStyle.Default) {
(action: UIAlertAction!) -> Voidin
...
NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextFieldTextDidChangeNotification, object: nil)
}
在顯示對話框之前,我們要凍結“好的”按鈕
Objective-C版本:
okAction.enabled = NO;
swift版本:
okAction.enabled =false
接下來,在通知觀察者(notification observer)中,我們需要在激活按鈕狀態前檢查“登錄”文本框的內容。
Objective-C版本:
- (void)alertTextFieldDidChange:(NSNotification *)notification{
UIAlertController *alertController = (UIAlertController *)self.presentedViewController;
if(alertController) {
UITextField *login = alertController.textFields.firstObject;
UIAlertAction *okAction = alertController.actions.lastObject;
okAction.enabled = login.text.length > 2;
}
}
swift版本:
func alertTextFieldDidChange(notification: NSNotification){
varalertController = self.presentedViewController as UIAlertController?
if(alertController != nil) {
varlogin = alertController!.textFields?.first as UITextField
varokAction = alertController!.actions.last as UIAlertAction
okAction.enabled = countElements(login.text) > 2
}
}
UIAlertController的登錄和密碼對話框示例
好了,現在對話框的“好的”按鈕被凍結了,除非在“登錄”文本框中輸入3個以上的字符:
上拉菜單
當需要給用戶展示一系列選擇的時候(選擇恐懼癥患者殺手),上拉菜單就能夠派上大用場了。和對話框不同,上拉菜單的展示形式和設備大小有關。在iPhone上(緊縮寬度),上拉菜單從屏幕底部升起。在iPad上(常規寬度),上拉菜單以彈出框的形式展現。
創建上拉菜單的方式和創建對話框的方式非常類似,唯一的區別是它們的形式。
Objective-C版本:
1
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"保存或刪除數據"message:@"刪除數據將不可恢復"preferredStyle: UIAlertControllerStyleActionSheet];
swift版本:
1
varalertController = UIAlertController(title:"保存或刪除數據", message:"刪除數據將不可恢復", preferredStyle: UIAlertControllerStyle.ActionSheet)
添加按鈕動作的方式和對話框相同。
Objective-C版本:
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消"style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *deleteAction = [UIAlertAction actionWithTitle:@"刪除"style:UIAlertActionStyleDestructive handler:nil];
UIAlertAction *archiveAction = [UIAlertAction actionWithTitle:@"保存"style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:cancelAction];
[alertController addAction:deleteAction];
[alertController addAction:archiveAction];
swift版本:
varcancelAction = UIAlertAction(title:"取消", style: UIAlertActionStyle.Cancel, handler: nil)
vardeleteAction = UIAlertAction(title:"刪除", style: UIAlertActionStyle.Destructive, handler: nil)
vararchiveAction = UIAlertAction(title:"保存", style: UIAlertActionStyle.Default, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
alertController.addAction(archiveAction)
您不能在上拉菜單中添加文本框,如果您強行作死添加了文本框,那么就會榮幸地得到一個運行時異常:
- Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Text fields can only be added to an alert controller of style UIAlertControllerStyleAlert’
同樣,簡單的異常說明,我們也不多說了。
接下來我們就可以在iPhone或者其他緊縮寬度的設備上展示了,不出我們所料,運行得很成功。
Objective-C版本:
[self presentViewController:alertController animated:YES completion:nil];
swift版本:
self.presentViewController(alertController, animated:true, completion: nil)
iPhone上的上拉菜單效果
如果上拉菜單中有“取消”按鈕的話,那么它永遠都會出現在菜單的底部,不管添加的次序是如何(就是這么任性)。其他的按鈕將會按照添加的次序從上往下依次顯示。《iOS 用戶界面指南》要求所有的“毀壞”樣式按鈕都必須排名第一(紅榜嘛,很好理解的,對不對?)。
別激動得太早,我們現在還有一個很嚴重的問題,這個問題隱藏得比較深。當我們使用iPad或其他常規寬度的設備時,就會得到一個運行時異常:
Terminating app due to uncaught exception ‘NSGenericException’, reason: ‘UIPopoverPresentationController (<_uialertcontrolleractionsheetregularpresentationcontroller: 0x7fc619588110="">) should have a non-nil sourceView or barButtonItem set before the presentation occurs.’
就如我們之前所說,在常規寬度的設備上,上拉菜單是以彈出框的形式展現。彈出框必須要有一個能夠作為源視圖或者欄按鈕項目的描點(anchor point)。由于在本例中我們是使用了常規的UIButton來觸發上拉菜單的,因此我們就將其作為描點。
在iOS 8中我們不再需要小心翼翼地計算出彈出框的大小,UIAlertController將會根據設備大小自適應彈出框的大小。并且在iPhone或者緊縮寬度的設備中它將會返回nil值。配置該彈出框的代碼如下:
Objective-C版本:
UIPopoverPresentationController *popover = alertController.popoverPresentationController;
if(popover){
popover.sourceView = sender;
popover.sourceRect = sender.bounds;
popover.permittedArrowDirections = UIPopoverArrowDirectionAny;
}
swift版本:
varpopover = alertController.popoverPresentationController
if(popover != nil){
popover?.sourceView = sender
popover?.sourceRect = sender.bounds
popover?.permittedArrowDirections = UIPopoverArrowDirection.Any
}
iPad上的上拉菜單效果
UIPopoverPresentationController類同樣也是在iOS 8中新出現的類,用來替換UIPopoverController的。這個時候上拉菜單是以一個固定在源按鈕上的彈出框的形式顯示的。
要注意UIAlertController在使用彈出框的時候自動移除了取消按鈕。用戶通過點擊彈出框的外圍部分來實現取消操作,因此取消按鈕便不再必需。
釋放對話框控制器
通常情況下,當用戶選中一個動作后對話框控制器將會自行釋放。不過您仍然可以在需要的時候以編程方式釋放它,就像釋放其他視圖控制器一樣。您應當在應用程序轉至后臺運行時移除對話框或者上拉菜單。假定我們正在監聽UIApplicationDidEnterBackgroundNotification通知消息,我們可以在observer中釋放任何顯示出來的視圖控制器。(參考在viewDidLoad方法中設立observer的示例代碼)。
Objective-C版本:
- (void)didEnterBackground:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
[self.presentedViewController dismissViewControllerAnimated:NO completion:nil];
}
swift版本:
func didEnterackground(notification: NSNotification){
NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextFieldTextDidChangeNotification, object: nil)
self.presentedViewController?.dismissViewControllerAnimated(false, completion: nil)
}
注意,要保證運行安全我們同樣要確保移除所有的文本框observer。
我們來總結一下
這篇文章比較長,但是希望能夠對您有所幫助。原先的UIAlertView和UIActionSheet類仍然可以在iOS 8中工作得很好,所以沒有必要急于更換代碼(要知道本文用到的許多函數盡在iOS 8中支持)。本文的代碼可以在我的Github主頁上找到,包括了AlertController - ObjC以及AlertController - swift。