天天看點

UIAlertController的使用

iOS 8的新特性之一就是讓接口更有适應性、更靈活,是以許多視圖控制器的實作方式發生了巨大的變化。全新的UIPresentationController在實作視圖控制器間的過渡動畫效果和自适應裝置尺寸變化效果(比如說旋轉)中發揮了重要的作用,它有效地節省了程式員們的工作量(天地良心啊)。還有,某些舊的UIKit控件也同樣發生了許多變化,比如說Alert Views、Action Sheets、Popovers以及Search Bar Controllers。本文将會對Alert Views和Action Sheets發生的改變進行一個大緻的介紹,我們會采用Objective-C和swift兩種語言同時進行代碼說明。

UIAlertView

随着蘋果上次iOS 5的釋出,對話框視圖樣式出現在了我們面前,直到現在它都沒有發生過很大的變化。下面的代碼片段展示了如何初始化和顯示一個帶有“取消”和“好的”按鈕的對話框視圖。

Objective-C版本:

1 2

UIAlertView *alertview = [[UIAlertView alloc] initWithTitle:@

"标題"

message:@

"這個是UIAlertView的預設樣式"

delegate:self cancelButtonTitle:@

"取消"

otherButtonTitles:@

"好的"

, nil];

[alertview show];

UIAlertController的使用

UIAlertView的預設樣式

swift版本和Objective-C版本不同,在swift中,alertView的初始化隻允許建立擁有一個取消按鈕的對話框視圖。或許您可以看到帶有otherButtonTitles的init方法,但是很遺憾,這個方法是沒有辦法通過編譯的。

1 2

var

alertView = UIAlertView(title: 

"标題"

, message: 

"這個是UIAlertView的預設樣式"

, delegate: self, cancelButtonTitle: 

"取消"

)

alertView.show()

UIAlertController的使用

swift版本的UIAlertView

要能夠建立和上面Objective-C版本相同的對話框視圖,我們可以采取曲線救國的方法,雖然麻煩了些,但是我們為了目的可以不擇手段的,是吧?

1 2 3 4 5 6 7

var

alertView = UIAlertView()

alertView.delegate = self

alertView.title = 

"标題"

alertView.message = 

"這個是UIAlertView的預設樣式"

alertView.addButtonWithTitle(

"取消"

)

alertView.addButtonWithTitle(

"好的"

)

alertView.show()

您也可以通過更改UIAlertView的alertViewStyle屬性來實作輸入文字、密碼甚至登入框的效果。

UIAlertController的使用

UIAlertView文本對話框

UIAlertController的使用

UIAlertView密碼對話框

UIAlertController的使用

UIAlertView登入對話框

UIAlertViewDelegate協定擁有響應對話框視圖的按鈕動作的回調方法。還有當文本框内容改變時,調用alertViewShouldEnableOtherButton:方法可以讓按鈕動态地可用或者不可用。

要說明一點,蘋果官方現在并不提倡在iOS 8中使用UIAlertView,取而代之的是UIAlertController。下面我們就來介紹UIAlertController的使用方法。

UIAlertController

在iOS 8中,UIAlertController在功能上是和UIAlertView以及UIActionSheet相同的,UIAlertController以一種子產品化替換的方式來代替這兩貨的功能和作用。是使用對話框(alert)還是使用上拉菜單(action sheet),就取決于在建立控制器時,您是如何設定首選樣式的。

一個簡單的對話框例子

您可以比較一下兩種不同的建立對話框的代碼,建立基礎UIAlertController的代碼和建立UIAlertView的代碼非常相似:

Objective-C版本:

1

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@

"标題"

message:@

"這個是UIAlertController的預設樣式"

preferredStyle:UIAlertControllerStyleAlert];

swift版本:

1

var

alertController = UIAlertController(title: 

"标題"

, message: 

"這個是UIAlertController的預設樣式"

, preferredStyle: UIAlertControllerStyle.Alert)

同建立UIAlertView相比,我們無需指定代理,也無需在初始化過程中指定按鈕。不過要特别注意第三個參數,要确定您選擇的是對話框樣式還是上拉菜單樣式。

通過建立UIAlertAction的執行個體,您可以将動作按鈕添加到控制器上。UIAlertAction由标題字元串、樣式以及當使用者選中該動作時運作的代碼塊組成。通過UIAlertActionStyle,您可以選擇如下三種動作樣式:正常(default)、取消(cancel)以及警示(destruective)。為了實作原來我們在建立UIAlertView時建立的按鈕效果,我們隻需建立這兩個動作按鈕并将它們添加到控制器上即可。

Objective-C版本:

1 2 3 4

UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@

"取消"

style:UIAlertActionStyleCancel handler:nil];

UIAlertAction *okAction = [UIAlertAction actionWithTitle:@

"好的"

style:UIAlertActionStyleDefault handler:nil];

[alertController addAction:cancelAction];

[alertController addAction:okAction];

swift版本:

1 2 3 4

var

cancelAction = UIAlertAction(title: 

"取消"

, style: UIAlertActionStyle.Cancel, handler: nil)

var

okAction = UIAlertAction(title: 

"好的"

, style: UIAlertActionStyle.Default, handler: nil)

alertController.addAction(cancelAction)

alertController.addAction(okAction)

最後,我們隻需顯示這個對話框視圖控制器即可:

Objective-C版本:

1

[self presentViewController:alertController animated:YES completion:nil];

swift版本:

1

self.presentViewController(alertController, animated: 

true

, completion: nil)

UIAlertController的使用

UIAlertController預設樣式

按鈕顯示的次序取決于它們添加到對話框控制器上的次序。一般來說,根據蘋果官方制定的《iOS 使用者界面指南》,在擁有兩個按鈕的對話框中,您應當将取消按鈕放在左邊。要注意,取消按鈕是唯一的,如果您添加了第二個取消按鈕,那麼你就會得到如下的一個運作時異常:

* Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘UIAlertController can only have one action with a style of UIAlertActionStyleCancel’

異常資訊簡潔明了,我們在此就不贅述了。

“警示”樣式

什麼是“警示”樣式呢?我們先不着急回答這個問題,先來看一下下面關于“警示”樣式的簡單示例。在這個示例中,我們将前面的示例中的“好的”按鈕替換為了“重置”按鈕。

Objective-C版本:

1 2

UIAlertAction *resetAction = [UIAlertAction actionWithTitle:@

"重置"

style:UIAlertActionStyleDestructive handler:nil];

[alertController addAction:resetAction];

swift版本:

1 2

var

resetAction = UIAlertAction(title: 

"重置"

, style: UIAlertActionStyle.Destructive, handler: nil)

alertController.addAction(resetAction)

UIAlertController的使用

“警示”樣式

可以看出,我們新增的那個“重置”按鈕變成了紅色。根據蘋果官方的定義,“警示”樣式的按鈕是用在可能會改變或删除資料的操作上。是以用了紅色的醒目辨別來警示使用者。

文本對話框

UIAlertController極大的靈活性意味着您不必拘泥于内置樣式。以前我們隻能在預設視圖、文本框視圖、密碼框視圖、登入和密碼輸入框視圖中選擇,現在我們可以向對話框中添加任意數目的UITextField對象,并且可以使用所有的UITextField特性。當您向對話框控制器中添加文本框時,您需要指定一個用來配置文本框的代碼塊。

舉個栗子吧,要重建立立原來的登入和密碼樣式對話框,我們可以向其中添加兩個文本框,然後用合适的占位符來配置它們,最後将密碼輸入框設定使用安全文本輸入。

Objective-C版本:

1 2 3 4 5 6 7 8

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@

"文本對話框"

message:@

"登入和密碼對話框示例"

preferredStyle:UIAlertControllerStyleAlert];

[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){

textField.placeholder = @

"登入"

;

}];

[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {

textField.placeholder = @

"密碼"

;

textField.secureTextEntry = YES;

}];

swift版本:

1 2 3 4 5 6 7 8 9

alertController.addTextFieldWithConfigurationHandler { 

(textField: UITextField!) -> Void 

in

textField.placeholder = 

"登入"

}

alertController.addTextFieldWithConfigurationHandler { 

(textField: UITextField!) -> Void 

in

textField.placeholder = 

"密碼"

textField.secureTextEntry = 

true

}

在“好的”按鈕按下時,我們讓程式讀取文本框中的值。

Objective-C版本:

1 2 3 4 5

UIAlertAction *okAction = [UIAlertAction actionWithTitle:@

"好的"

style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {

UITextField *login = alertController.textFields.firstObject;

UITextField *password = alertController.textFields.lastObject;

...

}];

swift版本:

1 2 3 4 5

var

okAction = UIAlertAction(title: 

"好的"

, style: UIAlertActionStyle.Default) {

(action: UIAlertAction!) -> Void 

in

var

login = alertController.textFields?.first as UITextField

var

password = alertController.textFields?.last as UITextField

}

如果我們想要實作UIAlertView中的委托方法alertViewShouldEnableOtherButton:方法的話可能會有一些複雜。假定我們要讓“登入”文本框中至少有3個字元才能激活“好的”按鈕。很遺憾的是,在UIAlertController中并沒有相應的委托方法,是以我們需要向“登入”文本框中添加一個Observer。Observer模式定義對象間的一對多的依賴關系,當一個對象的狀态發生改變時, 所有依賴于它的對象都得到通知并被自動更新。我們可以在構造代碼塊中添加如下的代碼片段來實作。

Objective-C版本:

1 2 3 4

[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){

...

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(alertTextFieldDidChange:) name:UITextFieldTextDidChangeNotification object:textField];

}];

swift版本:

1 2 3 4 5

alertController.addTextFieldWithConfigurationHandler {

(textField: UITextField!) -> Void 

in

...

NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector(

"alertTextFieldDidChange:"

), name: UITextFieldTextDidChangeNotification, object: textField)

}

當視圖控制器釋放的時候我們需要移除這個Observer,我們通過在每個按鈕動作的handler代碼塊(還有其他任何可能釋放視圖控制器的地方)中添加合适的代碼來實作它。比如說在okAction這個按鈕動作中:

Objective-C版本:

1 2 3 4

UIAlertAction *okAction = [UIAlertAction actionWithTitle:@

"好的"

style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {

...

[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];

}];

swift版本:

1 2 3 4 5

var

okAction = UIAlertAction(title: 

"好的"

, style: UIAlertActionStyle.Default) {

(action: UIAlertAction!) -> Void 

in

...

NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextFieldTextDidChangeNotification, object: nil)

}

在顯示對話框之前,我們要當機“好的”按鈕

Objective-C版本:

1

okAction.enabled = NO;

swift版本:

1

okAction.enabled = 

false

接下來,在通知觀察者(notification observer)中,我們需要在激活按鈕狀态前檢查“登入”文本框的内容。

Objective-C版本:

1 2 3 4 5 6 7 8

- (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版本:

1 2 3 4 5 6 7 8

func alertTextFieldDidChange(notification: NSNotification){

var

alertController = self.presentedViewController as UIAlertController?

if

(alertController != nil) {

var

login = alertController!.textFields?.first as UITextField

var

okAction = alertController!.actions.last as UIAlertAction

okAction.enabled = countElements(login.text) > 2

}

}

UIAlertController的使用

UIAlertController的登入和密碼對話框示例

好了,現在對話框的“好的”按鈕被當機了,除非在“登入”文本框中輸入3個以上的字元:

上拉菜單

當需要給使用者展示一系列選擇的時候(選擇恐懼症患者殺手),上拉菜單就能夠派上大用場了。和對話框不同,上拉菜單的展示形式和裝置大小有關。在iPhone上(緊縮寬度),上拉菜單從螢幕底部升起。在iPad上(正常寬度),上拉菜單以彈出框的形式展現。

建立上拉菜單的方式和建立對話框的方式非常類似,唯一的差別是它們的形式。

Objective-C版本:

1

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@

"儲存或删除資料"

message:@

"删除資料将不可恢複"

preferredStyle: UIAlertControllerStyleActionSheet];

swift版本:

1

var

alertController = UIAlertController(title: 

"儲存或删除資料"

, message: 

"删除資料将不可恢複"

, preferredStyle: UIAlertControllerStyle.ActionSheet)

添加按鈕動作的方式和對話框相同。

Objective-C版本:

1 2 3 4 5 6

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版本:

1 2 3 4 5 6

var

cancelAction = UIAlertAction(title: 

"取消"

, style: UIAlertActionStyle.Cancel, handler: nil)

var

deleteAction = UIAlertAction(title: 

"删除"

, style: UIAlertActionStyle.Destructive, handler: nil)

var

archiveAction = 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版本:

1

[self presentViewController:alertController animated:YES completion:nil];

swift版本:

1

self.presentViewController(alertController, animated: 

true

, completion: nil)

UIAlertController的使用

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版本:

1 2 3 4 5 6

UIPopoverPresentationController *popover = alertController.popoverPresentationController;

if

(popover){

popover.sourceView = sender;

popover.sourceRect = sender.bounds;

popover.permittedArrowDirections = UIPopoverArrowDirectionAny;

}

swift版本:

1 2 3 4 5 6

var

popover = alertController.popoverPresentationController

if

(popover != nil){

popover?.sourceView = sender

popover?.sourceRect = sender.bounds

popover?.permittedArrowDirections = UIPopoverArrowDirection.Any

}

UIAlertController的使用

iPad上的上拉菜單效果

UIPopoverPresentationController類同樣也是在iOS 8中新出現的類,用來替換UIPopoverController的。這個時候上拉菜單是以一個固定在源按鈕上的彈出框的形式顯示的。

要注意UIAlertController在使用彈出框的時候自動移除了取消按鈕。使用者通過點選彈出框的外圍部分來實作取消操作,是以取消按鈕便不再必需。

釋放對話框控制器

通常情況下,當使用者選中一個動作後對話框控制器将會自行釋放。不過您仍然可以在需要的時候以程式設計方式釋放它,就像釋放其他視圖控制器一樣。您應當在應用程式轉至背景運作時移除對話框或者上拉菜單。假定我們正在監聽UIApplicationDidEnterBackgroundNotification通知消息,我們可以在observer中釋放任何顯示出來的視圖控制器。(參考在viewDidLoad方法中設立observer的示例代碼)。

Objective-C版本:

1 2 3 4 5

- (void)didEnterBackground:(NSNotification *)notification

{

[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];

[self.presentedViewController dismissViewControllerAnimated:NO completion:nil];

}

swift版本:

1 2 3 4

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。

轉載位址:http://www.jianshu.com/p/86f933850df8 來自 星夜暮晨的部落格