How can I add custom textField to my alert? - ios

I can add textField to my alert as shown below using alert.addTextField()
let alert = UIAlertController(title: "Title", message: "Subtitle", preferredStyle: UIAlertController.Style.alert)
alert.addTextField()
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: { _ in
print(alert.textFields?[0].text ?? "")
}))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
However, if I have a custom textField, like CurrencyField as shared in https://stackoverflow.com/a/29783546/3286489, how could I add that to my alert (instead of just having a generic TextField)?

The Holy Path
The Apple-given API doesn't allow for you to use a custom subclass of UITextField. It does, however, allow you to customize the UITextField:
alert.addTextField { textField in
// customize text field
}
You'll have to try and port the functionality of your CurrencyView over, having only access to the APIs available by default on UITextField.
The Unholy Path
⚠️ Disclaimer: This is a Bad Idea. Using vendor APIs in ways they weren't intended to makes your code and application less stable.
Now that we've got that out of the way: you could also add a view directly to the default UITextField. You'd have to disable interaction/editing on the original UITextField, though, and make sure it only goes to your custom text field.
If you really wanted to go to the dark side, you could swizzle UITextField and force your CurrencyView to be initialized, but, once again, this is a Bad Idea.

Related

iOS: Which function to override when I need to do something after the view is visible to the user?

So the first thing my app does is get a list of movies from an API. I'm trying to handle if there's a network issue. Currently, in my viewDidLoad method, I call "updateApiInfo", which contains the following code:
if self.movies == nil {
print("stuff went wrong")
let alert = UIAlertController(title: "Network Error", message: "There was a nework error.\nYou must be connected to the internet to use Flicks.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Exit", style: UIAlertActionStyle.Default, handler: {(UIAlertAction) in
UIControl().sendAction(Selector("suspend"), to: UIApplication.sharedApplication(), forEvent: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
When viewDidLoad calls updateApiInfo, I get this message:
Warning: Attempt to present <UIAlertController: 0x7fad10e3ad80> on <Flicks.MoviesViewController: 0x7fad10e35cc0> whose view is not in the window hierarchy!
When I call updateApiInfo just from the user refreshing, the error pops up as expected.
I'm assuming that viewDidLoad only gets called before the view gets displayed to the user, which I guess is causing this error. Is there a method I can stick this code in for after the view is displayed to the user, so the problem can presumably get fixed?
You need to use viewDidAppear: to make sure the view is already in the window hierarchy.
There's a pretty good explanation of the order in which the methods are called here:
What is the process of a UIViewController birth (which method follows which)?

How to make message of UIAlertController copyable in swift2, iOS9?

I've a simple question: How can I make the message of an UIAlertController be selectable and copyable by the user?
The controller is initiated like so:
let alertController = UIAlertController(title: "Hello World", message: "Copy Me!", preferredStyle: .Alert)
and displayed like so:
presentViewController(alertController, animated: true, completion: nil)
Adam's correct that UIAlertController doesn't provide text selection functionality, so a traditional copy/paste solution isn't going to work. You could alternatively provide a button on your UIAlertController that copies a string to the pasteboard.
UIPasteboard.general.string = "Copy Me!"
It is not possible. UIAlertController has no such functionality. It was implemented with UILabel components, which don't support copying text. You are not allowed to subclass UIAlertController either. The only option is to implement your own controller instead.

No initializer for UIAlertAction (title, style, handler)

Dear Stackoverflowers,
Can any experts on Swift/UIKit see what I'm doing wrong in the following screenshot?
Xcode can't seem to find the convenience initializer for a UIAlertAction based on a title, style and handler, and I don't know of any other way to initialize a UIAlertAction. In practice, the handler won't be nil.
Thank you in advance,
Jamie
You are trying to pass Int to style param which is against swift rule. Try this out:
let saveAction = UIAlertAction(title: "Save", style: UIAlertActionStyle.Default, handler: nil)

Swift UIAlertController file rename action

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.

Unable to choose order of buttons in UIAlertController

I was under the impression that if the normal action is a destructive action and the other is a cancel action in their UIAlertController that the destructive one should be on the left and the cancel should be on the right.
If the normal action is not destructive, then the normal action should be on the right and the cancel should be on the left.
That said, I have the following:
var confirmLeaveAlert = UIAlertController(title: "Leave", message: "Are you sure you want to leave?", preferredStyle: .Alert)
let leaveAction = UIAlertAction(title: "Leave", style: .Destructive, handler: {
(alert: UIAlertAction!) in
//Handle leave
}
)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
confirmLeaveAlert.addAction(leaveAction)
confirmLeaveAlert.addAction(cancelAction)
self.presentViewController(confirmLeaveAlert, animated: true, completion: nil)
I was under the impression that if I add the leaveAction first, then the cancelAction that the leaveAction would be the button on the left. This was not the case. I tried adding the buttons in the opposite order as well and it also resulted in the buttons being added in the same order.
Am I wrong? Is there no way to achieve this?
My solution to this was to use the .Default style instead of .Cancel for the cancelAction.
Since iOS9 there is a preferredAction property on UIAlertController. It places action on right side. From docs:
When you specify a preferred action, the alert controller highlights the text of that action to give it emphasis. (If the alert also contains a cancel button, the preferred action receives the highlighting instead of the cancel button.)
The action object you assign to this property must have already been added to the alert controller’s list of actions. Assigning an object to this property before adding it with the addAction: method is a programmer error.

Resources