I want do display an alert message and I am using iOS SDK 8.1 with XCode 6.1. I know that UIAlertView is deprecated; however, my app has to support also iOS 7 and I have to use UIAlertView if the app is running on an iOS 7 device. This alert view has a text field and two buttons where one of them is default cancel button. The other button should be disabled as long as text field is empty.
Here is my code:
class MyViewController : UIViewController, UIAlertViewDelegate {
var addRecipientAlertView:UIAlertView?
// Irrelevant code here
func performSomething(someValue:String) {
addRecipientAlertView = UIAlertView(title: "Title", message: "Enter full name of user, email of user or a free-form text", delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "Add Recipient")
addRecipientAlertView!.alertViewStyle = UIAlertViewStyle.PlainTextInput
addRecipientAlertView!.accessibilityValue = someValue
// Text Field Settings
let textField:UITextField = addRecipientAlertView!.textFieldAtIndex(0)!
textField.placeholder = "Full Name, Email or Any Text"
textField.keyboardType = UIKeyboardType.EmailAddress
textField.clearButtonMode = UITextFieldViewMode.Always
addRecipientAlertView!.show()
}
}
func alertViewShouldEnableFirstOtherButton(alertView: UIAlertView) -> Bool {
return false
}
The problem is; whatever I have tried, the first other button was not disabled anyway. Finally I gave up trying to check the text of my text field and I have implemented the alertViewShouldEnableFirstOtherButton delegate method such that it always return false. However, the result has not changed and both of the buttons (named "Cancel" and "Add Recipient" in this example) are still enabled. What am I missing?
I had the same problem and I guess it's a bug in the SDK. The only working solution that I managed to come up with with was to implement an Objective-C class that showed the alert and served as its delegate.
Related
I have a requirement that the text in the buttons of a UIAlertController should be set to bold for every button (as opposed to the standard iOS behavior which is that the button assigned the style cancel is bold, or that for which preferredAction has been set is bold. The requirement is that all button text should be bold).
Is there a way to achieve this using a UIAlertController? Or will I be forced to created a custom dialog using a UIView?
There's plenty of past questions/answers on manipulating the body text for a UIAlertController using an attributed string, but I've not found anything for doing the equivalent for the text of the action buttons of an UIAlertController.
Buttons on UIAlertController are of UIAlertAction. There are no any setter methods on any property of UIAlertAction available, so you can't change the button text to bold.
Even title have to be String type, it doesn't accept NSAttributedString. So you should go with a custom dialog using a UIView.
Apple class reference for UIAlertAction:
#available(iOS 8.0, *)
open class UIAlertAction : NSObject, NSCopying {
public convenience init(title: String?, style: UIAlertActionStyle, handler: ((UIAlertAction) -> Swift.Void)? = nil)
open var title: String? { get }
open var style: UIAlertActionStyle { get }
open var isEnabled: Bool
}
Or will I be forced to created a custom dialog using a UIView?
Yes, that’s it. But it’s very easy, and gives you much more power and flexibility than UIAlertController. There are lots of sample projects out there, and once you’ve used one you may never go back to UIAlertController again!
I was wondering if it was possible to have an alert with preloaded text in it. For example, if my app guessed something I would want the guess to appear in the alert text box but the user could delete the text and change the answer if they wanted to.
Thank you
Have you had a look at this page : How to add a TextField to UIAlertView in Swift ?
You could put textfields (or anything else you'd like) inside the alertview and so that the user could interact with it more deeply :)
Vincent
yes you can. You can create a custom alertView from a xib and you can input there an UITextField ,so the user can have the opportunity to change the text
Simply Create an alert controller leave the title and message empty and add a TextField to your alert that way you can preload your text to the TextField and user can also edit the text
Just add text field in alert controller.
let alertController = UIAlertController(title: "title", message: "please enter words", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler { (txtUsername) -> Void in
usernameTextField?.text = "preload"
usernameTextField?.placeholder = "placeholder"
}
self.presentViewController(alertController, animated: true, completion: nil)
I am getting a very strange error. I think the compiler is trying to tell me that it can't segue to another view controller until it is done executing all the code in my current view controller but I am not sure.
I'm literally getting input by using an alert box (i.e. calling a function called generateTextField).
Then when I am done I'm saying "Hey I want you to go to another view controller" - but the the compiler instead tells me "Hey I don't think so".
Here is my error:
Warning: Attempt to present HairStyle1ViewController: 0x7...> on browseBarbersViewController: 0x7...> which is already presenting
Warning: Attempt to present HairStyle1ViewController: 0x7..> on browseBarbersViewController: 0x7...> which is already presenting
#IBAction func AddNewStyleButtonClicked(sender: AnyObject)
{
// Get the "hairstyle name" from the user
generateTextField();
// OK We are done with that function, now transition to the
// next screen
performSegueWithIdentifier("HairStyle1", sender: self);
}
// Generate a text field for user input (i.e. call the alert function)
func generateTextField()
{
//1. Create the alert controller.
var tempStyle = "";
var alert = UIAlertController(title: "Add a New Style", message: "Enter the name of the new hairstyle below", preferredStyle: .Alert);
//2. Add the text field. You can configure it however you need.
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.placeholder = "Your New Hairstyle Goes Here..";
})
//3. Grab the value from the text field, and print it when the user clicks OK.
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
let textField = alert.textFields![0] as UITextField
tempStyle = textField.text!;
print("New Style Added is: " + tempStyle);
HairStyle = tempStyle;
}))
// 4. Present the alert.
self.presentViewController(alert, animated: true, completion: nil)
}
It's also weird that when I take out the generateTextField() function it performs the segue perfectly. I'm very confused.
Wow, I figured it out. I had to instead, segue in the body of the alert function.
I fixed this by adding
self.performSegueWithIdentifier("HairStyle1", sender: self);
after the
HairStyle = tempStyle; line
I am using Swift 2. This question relates to iOS9.
In brief:-
If the file rename button is clicked and the file name is still invalid then do I need to present the alert again or is there a smarter way of handling this?
In full:-
Saving a file imported from iCloud, I am presenting a UIAlertController called alertController if a file with the same name (.lastPathComponent) already exists in /Documents.
The UIAlertController has two actions titled Cancel and Rename and a .addTextFieldWithConfigurationHandler. If the file name already exists then the user is prompted to either cancel or rename.
This question relates to validating the new name and re-presenting (or not dismissing) the UIAlertController until the file name is valid:
If the user clicks the rename button and the file name is still the same, or is the same as another file which already exists, then I want the UIAlertController to be shown again. Or better still it would not be dismissed until the file name is valid.
The way I have done this (the only way I have figured out) is by adding a func named presentAlertController which presents the UIAlertController. This func is called from the rename handler when the button is clicked if the file name already exists (or has not been changed). (The same as simply presenting the UIAlertController again from the action).
My question:-
My code does what I want but can anyone suggest a neater and less clumsy way of achieving this outcome - without having to present the UIAlertController again?
Here is the relevant code. Please note that this whole section is within the completion handler of another function - hence the need for various references to self and why the UIAlertController code is within a dispatch_async(dispatch_get_main_queue()) (has to be called from the main queue).
//...
if checkFileExists(saveURL) { // saveURL: NSURL
dispatch_async(dispatch_get_main_queue()) {
let message = "File named `\(saveURL.lastPathComponent!)` already exists. Please import using a new name or else cancel."
let alertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler { textField -> Void in
textField.text = saveURL.lastPathComponent! // it presents a text field containing the file name which needs to be changed
}
func presentAlertController() {
self.presentViewController(alertController, animated: true) {}
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Rename", style: .Default) { action -> Void in
saveURL = saveURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent((alertController.textFields?.first!.text)!)
if checkFileExists(saveURL) {
presentAlertController() // currently it is calling a function to present the UIAlertController again if the file still exists when the button is clicked
} else {
saveXML(saveURL, dataObject: self.myThing)
self.fileTableView.reloadData()
}
})
presentAlertController() // this will be the first time that this called
}
}
//...
So you can add a target to your text field that will be called when the text field is edited, and in that function you can check if the user has typed a valid name. The problem you'll have with that is you'll need access to alertController so you can disable the "Rename" button. You can accomplish that by making a property at the top of your view controller like so:
var alertController: UIAlertController!
then revise the code your posted like so:
//...
if checkFileExists(saveURL) { // saveURL: NSURL
dispatch_async(dispatch_get_main_queue()) {
let message = "File named `\(saveURL.lastPathComponent!)` already exists. Please import using a new name or else cancel."
//just assigning here, not redeclaring (get rid of "let")
alertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler { textField -> Void in
textField.text = saveURL.lastPathComponent! // it presents a text field containing the file name which needs to be changed
//Add the target here, calls on checkString
textField.addTarget(self, action: "checkString:", forControlEvents: UIControlEvents.EditingChanged)
}
func presentAlertController() {
self.presentViewController(alertController, animated: true) {}
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Rename", style: .Default) { action -> Void in
saveURL = saveURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent((alertController.textFields?.first!.text)!)
if checkFileExists(saveURL) {
presentAlertController() // currently it is calling a function to present the UIAlertController again if the file still exists when the button is clicked
} else {
saveXML(saveURL, dataObject: self.myThing)
self.fileTableView.reloadData()
}
})
//Disable the "Rename" button to start, remove this line if you don't want that to happen
(alertController.actions as! [UIAlertAction])[1].enabled = false
presentAlertController() // this will be the first time that this called
}
}
//...
Then you'll have to make a checkString function (obviously you can rename this however you like as long as you also change the selector on the line where you add the target to the text field). Here's a little bit of code to give you an idea, but you'll have to write your own stuff here.
func checkString(sender: UITextField) {
//Pretty safe assumption you don't want an empty string as a name
if sender.text == "" {
(alertController.actions as! [UIAlertAction])[1].enabled = false
}
//As soon as the user types something valid, the "Rename" button gets enabled
else {
(alertController.actions as! [UIAlertAction])[1].enabled = true
}
}
This code has been tested, but not very rigorously, so comment if you have problems or if it works.
Instances of UIActionSheet don't display correctly on iPads running iOS7. For some reason, they display the cancel button and leave off the last button. The same code works fine on iOS8.
You would expect the cancel button to be ignored, given that tapping elsewhere on the screen will close the action sheet. Does anyone know why this is the case?
Exactly the same code is used in both cases:
// Create UIActionSheet
let mapOptions = UIActionSheet(title: "Select map type", delegate: self, cancelButtonTitle: "Cancel", destructiveButtonTitle: nil, otherButtonTitles: "Standard", "Hybrid", "Satellite")
// Display from bar button
mapOptions.showFromBarButtonItem(self.mapTypeButton, animated: true)
I was able to solve the problem by avoiding the default UIActionSheet constructor and adding buttons individually. This - and making sure the cancel button is added last - resolves the issue.
// Create the UIActionSheet
var mapOptions = UIActionSheet()
mapOptions.title = "Select map type"
mapOptions.addButtonWithTitle("Standard")
mapOptions.addButtonWithTitle("Hybrid")
mapOptions.addButtonWithTitle("Satellite")
// Add a cancel button, and set the button index at the same time
mapOptions.cancelButtonIndex = mapOptions.addButtonWithTitle("Cancel")
mapOptions.delegate = self
// Display the action sheet
mapOptions.showFromBarButtonItem(self.mapTypeButton, animated: true)
I'm facing the same problem with Swift. Your answer saved me.
I found you can still use this init method (with cancel button nil) to make your code more succinct.
let mapOptions = UIActionSheet(title: "Select map type", delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil, otherButtonTitles: "Standard", "Hybrid", "Satellite")
and then with the line you provided
mapOptions.cancelButtonIndex = mapOptions.addButtonWithTitle("Cancel")
(I don't have enough reputation to add a comment so write here instead.)