I've read a few examples on declaring handlers for UIAlertViews and somehow I just can't grasp the concept of the proper syntax. I've seen a few examples where they do one of the following:
- handler: someFunction
- handler: {action in SomeFunction} (what does "action in" mean?)
- handler: { _ in print("Foo!") (again, what does "in" mean?)
My biggest concern is that I don't know what these things mean. and I'm trying to use the first style however I get the following error: "Variable used within its own initial value"
let answerVCAlert = UIAlertController(title: "Your turn", message: "What's the answer?", preferredStyle: .alert)
let submitAnswer = UIAlertAction(title: "Submit", style: .default, handler: submitAnswer(answer: " ")) //grab from textfield
let noAnswer = UIAlertAction(title: "No Answer", style: .default, handler: submitAnswer(answer: " "))
func submitAnswer(answer: String) {
print ("The string passed is \(answer)")
//compare answer to correct answer
}
func attemptAnswer() {
answerVCAlert.addAction(submitAnswer)
answerVCAlert.addAction(noAnswer)
//answerVCAlert.addTextField //how ??? too many different examples
self.present(answerVCAlert, animated: true, completion: nil)
}
As mentioned in the comments, now's the time to learn how to using closures or completion handlers. It's time well spent.
Here's some working code (based on your's but with some minor changes) that also includes a textfield. A good Swift 3 example of getting the text from an alert textfield is this SO answer. There is a change in Swift 4 syntax in my code below.
I've indented the code in hopes it helps you understand completion handlers better. I added some inline comments also.
Here's the alert:
// I changed the name of your alert controller for brevity
let alertVC = UIAlertController(
title: "Your turn",
message: "What's the answer?",
preferredStyle: .alert)
// Add a textfield called answerText. You may not want the placeholder to be blank.
alertVC.addTextField(configurationHandler: {(answerText: UITextField!) in
answerText.placeholder = "Default answer"
})
// Add a Submit button that will call submitAnswer()
let submitAnswer = UIAlertAction(
title: "Submit",
style: .default,
handler: { action -> Void in
self.submitAnswer(alertVC.textFields![0])
})
alertVC.addAction(submitAnswer)
// Add a No Answer button that will call noAnswer()
// NOTE: I changed this to be style of cancel... check out how it's rendered.
let noAnswer = UIAlertAction(
title: "No Answer",
style: .cancel,
handler: { action -> Void in
self.noAnswer()
})
alertVC.addAction(noAnswer)
// I think you *want* everybody to try to answer, so let's make that the preferred action.
alertVC.preferredAction = submitAnswer
present(
alertVC,
animated: true,
completion: nil)
And here's the results:
func submitAnswer(_ answer: String) {
print ("The answer is \(answer)")
//compare answer to correct answer
}
func noAnswer() {
print ("Cancel was tapped.")
}
Among the changes I made are:
Changed the style of one action from .default to .cancel to give you an idea of what that does.
Added self to the completion handler calls - they are required.
Changed the signature of submitAnswer() to no longer need the parameter label. It's Swiftier that way.
Changed the noAnswer action call from submitAnswer() to noAnswer() to distinguish what the user tapped on.
Related
Based on the following code, commands in closure in alert function are executed before User interface be executed. It causes, the variables in closure be empty?
func callAlert2() {
let alert = UIAlertController(title: "Add Contact", message: "Please fill the form", preferredStyle: .alert)
alert.addTextField { textField in
textField.placeholder = "Name"
self.contact.name = textField.text
}
alert.addTextField { (textField) in
textField.placeholder = "Surname"
self.contact.surname = textField.text
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { (action) in
if let name = alert.textFields?.first?.text {
self.allContacts.append(self.contact)
}
}))
self.present(alert, animated: true, completion: nil)
}
Seems like you miss understood addTextField(configurationHandler:) as per docs
A block for configuring the text field prior to displaying the alert.
This block has no return value and takes a single parameter
corresponding to the text field object. Use that parameter to change
the text field properties.
Link: https://developer.apple.com/documentation/uikit/uialertcontroller/1620093-addtextfield
You are expecting your self.contact object's name and surname to be updated inside configuration block but that will never happen. This closure/block is intended only for configuring the textfield and not to capture user input.
Inside this closure you can modify textFields property like placeholder, background color, border etc etc to configure it before being rendered.
But if you want to capture the user input, use actions closure.
Rather
alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { (action) in
if let name = alert.textFields?.first?.text,
(alert.textFields?.count ?? 0) >= 1,
let surname = alert.textFields?[1].text {
self.contact.name = name
self.contact.surname = surname
self.allContacts.append(self.contact)
}
}))
Hope this helps
I have written a function that brings up an alert that requires the user to make a menu choice. That choice will be used to change a button name. The problem is the button name is changed before the user choice is made. It is changed to the previous function result.
I have read that a handler can be used to delay execution of the result, but I can't figure out how to use it.
#IBAction func selectProp(_ sender: Any) {
propName.setTitle(menulist(title:"propName", message:""), for: .normal)
print("selected property = ", choice) // }
}
func menulist (title: String, message: String) -> String {
let title1 = "Select Property"
let alert = UIAlertController(title: title1, message: message, preferredStyle:UIAlertControllerStyle.alert)
let k = rentals.count
if k > 0 {
for i in 0 ... k-1 {
alert.addAction(UIAlertAction(title:rentals[i], style: .default, handler: {action in
choice = rentals[i]
print("choice=",choice)
}))
}
alert.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: {action in
choice = "Select"
print("choice=",choice)
}))
self.present(alert, animated: true, completion: nil)
}
return choice
}
The problem is the button name is changed before the user choice is made and the print statement is executed before the user makes the choice. The results, button change and print, are based on the previous user input choice.
I have read that a handler can be used to delay execution of the result, but can't figure out how to use it.
Exactly, such handlers are called closures. Since UIAlertAction uses closure to deliver the result, you need to use closure in your function instead of returning value.
func showMenuList(title: String, message: String, completion: #escaping (_ rental: String?) -> ()) {
let title = "Select Property"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
// loop through all rentals and add action to alert for each of them
rentals.forEach { (rental) in
let rentalAction = UIAlertAction(title: rental, style: .default, handler: { _ in
completion(rental)
})
alert.addAction(rentalAction)
}
// add cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
completion(nil)
})
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}
Then you can use this function like this:
showMenuList(title: "propName", message: "") { result in
guard let result = result else {
// handle Cancel action
return
}
// handle property selected
print(result)
}
I think I see what's happening here. The #IBAction func selectProp() is being called due to an action (.touchUpInside perhaps?), and it is setting the propName before the user has selected their choice using the alert.
What you need to do here is move the propName.setTitle(...) into the handler itself, so it is called at the point in time that the user selects the option via the alert:
alert.addAction(UIAlertAction(title:rentals[i], style: .default, handler: {action in
propName.setTitle(rentals[i], for: .normal)
....
At the moment, the propName title is being set when the user triggers selectProp, not when the user selects their choice via the alert.
Hope that helps!
Let me know if you have any more questions.
I'm trying to make my life a little bit easier and my app more maintainable while at the same time reducing the amount of duplicated code. So I thought it would be nice to put the some code to display a certain type of UIAlertController into its own class.
The thing is, that I have basically the same alert which only differs really slightly based on where I display it in my app. So I thought to myself: why not use a enum and every time I want to display an alert of that kind, just give it the enum value. Get rid of all the duplicated strings everywhere in my code.
I love enums in Swift. They are just so schwifty - ehh I mean swifty. So I came up with this example enum:
enum MyAlertType {
case a
case b
}
Next I created a simple class to handle the display:
class MyAlert {
static func showAlert(ofType type: MyAlertType, inViewController viewController: UIViewController, handler: ((UIAlertAction) -> ())? = nil, completion: (() -> ())? = nil) {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
viewController.present(alert, animated: true, completion: completion)
}
}
//Somewhere in my code (obviously a UIViewController)
MyAlert.showAlert(ofType: .a), inViewController: self)
But wait, I still have to give this function the ViewController where I want to display the alert in. For me thats always the same (self) so the next logical step was to make this an extension:
extension UIViewController {
func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())? = nil, completion: (() -> ())? = nil) {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
present(alert, animated: true, completion: completion)
}
}
//Somewhere in my UIViewController)
showAlert(ofType: .a)
But this makes the code available for all UIViewControllers, even those where I don't explicitly need/want to display that kind of alert. I mean, yeah sure, I am the developer and I can decide not to use this but on the other hand isn't it always a good practice to hide everything as much as possible and as least as necessary? Who knows who might in the future join my dev team and start misusing my beautiful code in ways I haven't thought of?
The other day I learned about Swifts protocol oriented programming approach (which to be honest I still have not fully understood by now) and now I think, I maybe should make this a protocol with a default implementation and then let the only those UIViewControllers implement the protocol, where I need to show this alert.
protocol MyAlertProtocol {
func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())?, completion: (() -> ())?)
}
extension MyAlertProtocol where Self : UIViewController {
func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())? = nil, completion: (() -> ())? = nil) {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
present(alert, animated: true, completion: completion)
}
}
extension MyViewController: MyAlertProtocol {}
I know that this might sound as an opinion based question so I'm not asking you if you think if this or that is better but to tell me if there actually is a best practice for this scenario and how it looks like.
Protocol with default implementation via protocol extension? Simple UIViewController extension? Custom Enum, Struct or Class with static function (and if so, which one)? Or maybe even just a function somewhere in a Swift file? I feel overwhelmed. The agony of choice...
UPDATE/SOLUTION
After reading the given answers, I decided that my Sergeys answer was indeed the most suitable one. I wanted to make my like "easier" by having "less lines of duplicated code". For me this included the "presend(controller:animated:)" inside my ViewController.
However, I think you guys are right. I should use a struct (a class is really not necessary for one static function) with a static func to generate the alert man make it "ready to use" but still let the caller decide where to present it.
By doing it like this, I could use my alert-generation-struct anywhere I want and for example let a delegate present it or pass it around until I reach a UIViewController who can present it, if my caller isn't one.
Therefore in my very simple case, I'll go with:
enum MyAlertType {
case a
case b
}
struct MyAlert {
static func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())? = nil) -> UIAlertController {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
return alert
}
}
Thanks to everybody participating in dissolving the blockade in my head.
Making static function is common and convenient approach. Please, pay attention that type you created is only for namespacing.
Alec O's answer is nice, but sometimes you want to pass some action you want to perform when you press OK button. Also I would pick struct instead of class.
Here is my version of making alert:
struct Alert {
static func errorAlert(title: String, message: String?, cancelButton: Bool = false, completion: (() -> Void)? = nil) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let actionOK = UIAlertAction(title: "OK", style: .default) {
_ in
guard let completion = completion else { return }
completion()
}
alert.addAction(actionOK)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
if cancelButton { alert.addAction(cancel) }
return alert
}
And usage in ViewController's subclass:
// Creating alerts:
let simpleAlert = Alert.errorAlert(title: "Error", message: "Simple message")
let alertWithCompletionAndCancel = Alert.errorAlert(title: "Message", message: "Message", cancelButton: true) {
// do something awesome
}
// Presenting alerts:
present(simpleAlert, animated: true)
present(alertWithCompletionAndCancel, animated: true)
I would suggest returning the alert from the function for the view controller to show. The custom alert class should not be presenting anything, if your aim is to use best practices.
class MyAlert {
static func generateAlert(ofType type: MyAlertType) -> UIAlertController {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
return alert
}
}
This will be the code that you repeat throughout the application. If multiple alerts are used in one view controller then you can initialized MyAlert globally for that view controller.
let myAlert = MyAlert()
let alert = myAlert.generateAlert(ofType: MyAlertType.a)
self.present(alert)
![This is the way I did
]1
When I want to present an alert, I did this:
let alertControl = AlertController.shared.alert(alertTitle: "Error Login", msg: "Please provide your email and password to login", style: .cancel, titleForAlertBtn: "OK")
present(alertControl, animated: true, completion: nil)
Apple's iOS UX guidelines show the following example:
...where:
When the most likely button performs a nondestructive action, it
should be on the right in a two-button alert. The button that cancels
this action should be on the left.
When the most likely button performs a destructive action, it should
be on the left in a two-button alert. The button that cancels this
action should be on the right.
Source: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/Alerts.html
However, it does not seem possible to implement this guidance with the current iOS APIs (testing on iOS 9 with Swift). Here's one attempt. But I've tried varying the order of adding the alert actions, and changing which action is .Default vs. .Cancel style. No combination appears to work.
#IBAction func showAlertAction(sender: AnyObject)
{
let title = "An Alert that Offers Two Alternatives Is Easy for People to Use"
let alertViewController = UIAlertController(title: title, message: nil, preferredStyle: UIAlertControllerStyle.Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: { (alert :UIAlertAction!) -> Void in
print("'Cancel' tapped")
})
alertViewController.addAction(cancelAction)
let safeChoiceAction = UIAlertAction(title: "Safe Choice", style: UIAlertActionStyle.Default, handler: { (alert :UIAlertAction!) -> Void in
print("'Safe Choice' tapped")
})
alertViewController.addAction(safeChoiceAction)
self.presentViewController(alertViewController, animated: true, completion: nil)
}
Result:
Couldn't find a clear and informative explanation for this.
After searching a while on a subject I didn't
find a clear explanation , even in it's class reference
UIAlertController Reference
It is ok, but not clear enough for me.
So after collecting some peaces I decided to make my own explanation
(Hope it helps)
So here it goes:
UIAlertView is deprecated as pointed out :
UIAlertView in Swift
UIAlertController should be used in iOS8+
so to create one first we need to instantiate it,
the Constructor(init) gets 3 parameters:
2.1 title:String -> big-bold text to display on the top of alert's dialog box
2.2 message:String -> smaller text (pretty much explains it's self)
2.3 prefferedStyle:UIAlertControllerStyle -> define the dialog box style, in most cases: UIAlertControllerStyle.Alert
Now to actually show it to the user, we can use showViewController or presentViewController and pass our alert as parameter
To add some interaction with a user we can use:
4.1
UIAlertController.addAction to create buttons
4.2
UIAlertController.addTextField to create text fields
Edit note: code examples below, updated for swift 3 syntax
Example 1: Simple Dialog
#IBAction func alert1(sender: UIButton) {
//simple alert dialog
let alert=UIAlertController(title: "Alert 1", message: "One has won", preferredStyle: UIAlertControllerStyle.alert);
//show it
show(alert, sender: self);
}
Example 2: Dialog with one input textField & two buttons
#IBAction func alert2(sender: UIButton) {
//Dialog with one input textField & two buttons
let alert=UIAlertController(title: "Alert 2", message: "Two will win too", preferredStyle: UIAlertControllerStyle.alert);
//default input textField (no configuration...)
alert.addTextField(configurationHandler: nil);
//no event handler (just close dialog box)
alert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.cancel, handler: nil));
//event handler with closure
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.default, handler: {(action:UIAlertAction) in
let fields = alert.textFields!;
print("Yes we can: "+fields[0].text!);
}));
present(alert, animated: true, completion: nil);
}
Example 3: One customized input textField & one button
#IBAction func alert3(sender: UIButton) {
// one input & one button
let alert=UIAlertController(title: "Alert 3", message: "Three will set me free", preferredStyle: UIAlertControllerStyle.alert);
//configured input textField
var field:UITextField?;// operator ? because it's been initialized later
alert.addTextField(configurationHandler:{(input:UITextField)in
input.placeholder="I am displayed, when there is no value ;-)";
input.clearButtonMode=UITextFieldViewMode.whileEditing;
field=input;//assign to outside variable(for later reference)
});
//alert3 yesHandler -> defined in the same scope with alert, and passed as event handler later
func yesHandler(actionTarget: UIAlertAction){
print("YES -> !!");
//print text from 'field' which refer to relevant input now
print(field!.text!);//operator ! because it's Optional here
}
//event handler with predefined function
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.default, handler: yesHandler));
present(alert, animated: true, completion: nil);
}
Hope It helps, and good luck ;-)
An instance of the UIAlertController can be presented modally on screen just as any other UIViewController using the presentViewController:animated:completion: method. What makes the UIAlertController instance differentiate between working as an ActionSheet or as an AlertView is the style parameter you pass when creating it.
No more delegation
If you have used a UIActionSheet or UIAlertView, you know that the way to get a callback from it is for a class (in most cases the ViewController) to implement the UIActionSheetDelegate or UIAlertViewDelegate protocol. There are some open source projects that replaced this delegation pattern with block based callbacks, but the official APIs were never updated. UIAlertController does not use delegation. Instead, it has a collection of UIAlertAction items, that use closures (or blocks if you are using Objective-C) to handle user input.
For Action Sheet
#IBAction func showActionSheet(sender: AnyObject) {
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Action Sheet", message: "Swiftly Now! Choose an option!", preferredStyle: .ActionSheet)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//Just dismiss the action sheet
}
actionSheetController.addAction(cancelAction)
//Create and add first option action
let takePictureAction: UIAlertAction = UIAlertAction(title: "Take Picture", style: .Default) { action -> Void in
//Code for launching the camera goes here
}
actionSheetController.addAction(takePictureAction)
//Create and add a second option action
let choosePictureAction: UIAlertAction = UIAlertAction(title: "Choose From Camera Roll", style: .Default) { action -> Void in
//Code for picking from camera roll goes here
}
actionSheetController.addAction(choosePictureAction)
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
For AlertView with Text Field
#IBAction func showAlert(sender: AnyObject) {
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Alert", message: "Swiftly Now! Choose an option!", preferredStyle: .Alert)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//Do some stuff
}
actionSheetController.addAction(cancelAction)
//Create and an option action
let nextAction: UIAlertAction = UIAlertAction(title: "Next", style: .Default) { action -> Void in
//Do some other stuff
}
actionSheetController.addAction(nextAction)
//Add a text field
actionSheetController.addTextFieldWithConfigurationHandler { textField -> Void in
//TextField configuration
textField.textColor = UIColor.blueColor()
}
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
Some of the syntax has changed since the original responses. Here is some sample code that alerts the user if they are not signed in to iCloud.
CKContainer.default().accountStatus { (accountStatus, error) in
switch accountStatus {
case .available:
print("iCloud Available")
case .noAccount:
print("No iCloud account")
//simple alert dialog
let alert=UIAlertController(title: "Sign in to iCloud", message: "This application requires iClound. Sign in to your iCloud account to write records. On the Home screen, launch Settings, tap iCloud, and enter your Apple ID. Turn iCloud Drive on. If you don't have an iCloud account, tap Create a new Apple ID", preferredStyle: UIAlertControllerStyle.alert);
//no event handler (just close dialog box)
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil));
//show it
self.present(alert, animated: true, completion: nil)
case .restricted:
print("iCloud restricted")
case .couldNotDetermine:
print("Unable to determine iCloud status")
}
}