Is it possible to save the text entered into an alert's textfield in SwiftUI and display it in the app like this -> Text("alert text input here")? - swift

So far I was able to handle displaying a textfield in an alert in which a user can input some text, but have not been able to use that text and display it inside the app.
Here's the code for the alert:
private func alert() {
let alert = UIAlertController(title: "Enter Name", message: "...or pseudo", preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Enter something"
}
alert.addAction(UIAlertAction(title: "Done", style: .default) { _ in })
let textField = alert.textFields![0] as UITextField
alertInput = textField.text ?? "Name"
showAlert(alert: alert)
}
func showAlert(alert: UIAlertController) {
if let controller = topMostViewController() {
controller.present(alert, animated: true)
}
}
And this is how I'm trying to implement it in the body:
Section {
VStack {
Button(action: {
withAnimation {
self.alert()
}
}) {
Text(alertInput)
}
}
}
alertInput is initialized before the body like so:
#State private var alertInput = ""

You will want to grab the value of the text field from inside the Done button's handler. So all you need to do is move the bracket down:
alert.addAction(UIAlertAction(title: "Done", style: .default) { _ in
let textField = alert.textFields![0] as UITextField
alertInput = textField.text ?? "Name"
})
The code inside this closure is called when the user taps this UIAlertAction you have set up.
Note that if the field is left empty, the value of textField.text will be "" and not nil, so in order to use the default value you provide, you may need some additional logic here to check for a blank string as well.

Related

How to add checkmark to submenu of UIMenu in Swift Storyboard?

I'm trying to create a filter option for an app in Swift. Currently, the UI will add a check mark if one of the category filters are selected, such as in the image below for "Food". However, if the "Within Radius" filter is selected, the UI doesn't get updated.
At the moment, this is the code I've written to add the checkmark:
private func updateActionState(actionTitle: String? = nil, menu: UIMenu) -> UIMenu {
if let actionTitle = actionTitle {
menu.children.forEach { action in
guard let action = action as? UIAction else {
return
}
if action.title == actionTitle {
if(action.state == .on){
action.state = .off
}
else{
action.state = .on
}
}
else{
action.state = .off
}
}
} else {
let action = menu.children.first as? UIAction
action?.state = .on
}
return menu
}
I created the menu as follows:
private lazy var elements: [UIAction] = [food, clothing, furniture, other]
private lazy var menu = UIMenu(title: "Category", children: elements)
private lazy var deferredMenu = UIMenu(title: "Distance", options: .displayInline, children: [self.distanceRadius])
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
loadMap();
menu = menu.replacingChildren([food, clothing, furniture, other, deferredMenu])
navigationItem.leftBarButtonItem?.menu = menu
}
And the UIAction is declared as:
private lazy var distanceRadius = UIAction(title: "Within Radius", attributes: [], state: currFilter == "Within Radius" ? .on : .off){action in
var alert = UIAlertController(title: "Radius", message: "Filter within a radius (in miles)", preferredStyle: .alert)
//2. Add the text field. You can configure it however you need.
alert.addTextField(configurationHandler: { (textField) -> Void in
textField.text = ""
})
//3. Grab the value from the text field, and print it when the user clicks OK.
var radius = 0
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak alert] (action) -> Void in
let textField = (alert?.textFields![0])! as UITextField
radius = Int(textField.text!) ?? 0
self.toggleFilter(actionTitle: "Within Radius", radius: radius)
}))
// 4. Present the alert.
self.present(alert, animated: true, completion: nil)
self.navigationItem.leftBarButtonItem?.menu = self.updateActionState(actionTitle: "Within Radius", menu: self.menu);
}
However, for the "Within Radius" option, the action.title is "Distance", as opposed to "Within Radius", which is what the UIAction is created with. Is there any way to cast a UIAction as a UIMenu to access the children within distanceRadius?
Or is there another way to get the check mark to appear in the Distance submenu?
I've tried re-calling updateActionState on deferredMenu as well but that did not do anything.

Disable Swift UIAlertController button until input validated

I have a tableview, with a navigation button that when pressed prompts a user, via an alert controller, to enter a category which will then be inserted into the table. I don't want users to submit an empty string or a category that's already present. This is the code I have so far:
#objc func promptForCategory() {
let ac = UIAlertController(title: "Enter a category", message: nil, preferredStyle: .alert)
ac.addTextField()
let submitCategory = UIAlertAction(title: "Enter", style: .default) { [unowned self, ac] (action: UIAlertAction) in
let answer = ac.textFields![0]
self.enter(answer: answer.text!)
}
ac.addAction(submitCategory)
ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(ac, animated: true)
}
func enter(answer: String) {
if isBlank(answer: answer) {
if doesContain(answer: answer) {
categories.append(answer)
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
}
func isBlank(answer: String) -> Bool {
return answer != ""
}
func doesContain(answer: String) -> Bool {
let uppercased = categories.map {$0.uppercased()}
return !uppercased.contains(answer)
}
Is there a way to disable the 'Enter' button until it passes the validation test?
You can obviously just put
any code you want in the👇
func okHandler(alert: UIAlertAction!)
{
// Whatever code you want
}
yourAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: okHandler))
It will be up to you what is needed.
Now,
I couldn't find the answer I was looking for on these posts so I had to figure it out.
If anyone needs to dismiss the UIAlert without pressing the button above, this is what I did for my application.
I wanted it to disappear before the button was pressed so it didn't interfere with other things.
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute:
{
yourAlert.dismiss(animated: true, completion: nil)
})
use this alert use validation rule TextValidationRule.nonEmpty
import UIKit
/// A validation rule for text input.
public enum TextValidationRule {
/// Any input is valid, including an empty string.
case noRestriction
/// The input must not be empty.
case nonEmpty
/// The enitre input must match a regular expression. A matching substring is not enough.
case regularExpression(NSRegularExpression)
/// The input is valid if the predicate function returns `true`.
case predicate((String) -> Bool)
public func isValid(_ input: String) -> Bool {
switch self {
case .noRestriction:
return true
case .nonEmpty:
return !input.isEmpty
case .regularExpression(let regex):
let fullNSRange = NSRange(input.startIndex..., in: input)
return regex.rangeOfFirstMatch(in: input, options: .anchored, range: fullNSRange) == fullNSRange
case .predicate(let p):
return p(input)
}
}
}
extension UIAlertController {
public enum TextInputResult {
/// The user tapped Cancel.
case cancel
/// The user tapped the OK button. The payload is the text they entered in the text field.
case ok(String)
}
/// Creates a fully configured alert controller with one text field for text input, a Cancel and
/// and an OK button.
///
/// - Parameters:
/// - title: The title of the alert view.
/// - message: The message of the alert view.
/// - cancelButtonTitle: The title of the Cancel button.
/// - okButtonTitle: The title of the OK button.
/// - validationRule: The OK button will be disabled as long as the entered text doesn't pass
/// the validation. The default value is `.noRestriction` (any input is valid, including
/// an empty string).
/// - textFieldConfiguration: Use this to configure the text field (e.g. set placeholder text).
/// - onCompletion: Called when the user closes the alert view. The argument tells you whether
/// the user tapped the Close or the OK button (in which case this delivers the entered text).
public convenience init(title: String, message: String? = nil,
cancelButtonTitle: String, okButtonTitle: String,
validate validationRule: TextValidationRule = .noRestriction,
textFieldConfiguration: ((UITextField) -> Void)? = nil,
onCompletion: #escaping (TextInputResult) -> Void) {
self.init(title: title, message: message, preferredStyle: .alert)
/// Observes a UITextField for various events and reports them via callbacks.
/// Sets itself as the text field's delegate and target-action target.
class TextFieldObserver: NSObject, UITextFieldDelegate {
let textFieldValueChanged: (UITextField) -> Void
let textFieldShouldReturn: (UITextField) -> Bool
init(textField: UITextField, valueChange: #escaping (UITextField) -> Void, shouldReturn: #escaping (UITextField) -> Bool) {
textFieldValueChanged = valueChange
textFieldShouldReturn = shouldReturn
super.init()
textField.delegate = self
textField.addTarget(self, action: #selector(TextFieldObserver.textFieldValueChanged(sender:)), for: .editingChanged)
}
#objc func textFieldValueChanged(sender: UITextField) {
textFieldValueChanged(sender)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return textFieldShouldReturn(textField)
}
}
var textFieldObserver: TextFieldObserver?
// Every `UIAlertAction` handler must eventually call this
func finish(result: TextInputResult) {
// Capture the observer to keep it alive while the alert is on screen
textFieldObserver = nil
onCompletion(result)
}
let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel, handler: { _ in
finish(result: .cancel)
})
let okAction = UIAlertAction(title: okButtonTitle, style: .default, handler: { [unowned self] _ in
finish(result: .ok(self.textFields?.first?.text ?? ""))
})
addAction(cancelAction)
addAction(okAction)
preferredAction = okAction
addTextField { textField in
textFieldConfiguration?(textField)
textFieldObserver = TextFieldObserver(textField: textField, valueChange: { textField in
okAction.isEnabled = validationRule.isValid(textField.text ?? "")
}, shouldReturn: { textField -> Bool in
return validationRule.isValid(textField.text ?? "")
})
}
// Start with a disabled OK button if necessary
okAction.isEnabled = validationRule.isValid(textFields?.first?.text ?? "")
}
}
// Example usage in view Controller
let alert = UIAlertController(title: "Alert", cancelButtonTitle: "Cancel", okButtonTitle: "Ok", validate: TextValidationRule.nonEmpty) { _ in
}
present(alert, animated: true, completion: nil)
You can disable/enable UIAlertAction by using
alertAction.isEnabled = false (or) true
in your code
submitCategory.isEnabled = false
or
ac.actions.first?.isEnabled = false

UIAlertController is disappearing automatically after short time

If a textfield is empty, I would like to make an UIAlertController to remind, that something has to be filled out in the textfield. Now I have the following code:
#IBAction func saveDetails(segue: UIStoryboardSegue) {
let dController = segue.source as? EntryTableViewController
if let text = dController?.bezeichnungTextField.text, text.isEmpty {
let alert = UIAlertController(title: "No description", message: "Please fill out description", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "OK", style: .default)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
}else {
guard let menge = dController?.mengeTextField.text, let bezeichnung = dController?.bezeichnungTextField.text, let kategorie = dController?.categoryButtonOutlet.titleLabel?.text else {return}
self.saveItem(menge: menge, bezeichnung: bezeichnung, kategorie: kategorie)
self.tableView.reloadData()
}
}
Now actually it works when I press the button to return to the first controller. But the UIAlertController only appears for a very short moment and then disappears automatically. Is there a mistake in my code or isn't it possible to call the UIAlertController on a unwind segue?
Thank you for your help
You just show the alert controller and then immediately unwind. The solution is to check for the empty textfield before unwinding.
Make IBAction for your button then check the textfield if it is empty and then perforn your segue programmatically.

Getting text value from alert box

I'm having problems getting a text fields value from a UIAlertController. I am getting the value, but It seems to be executing my code in the wrong order. In the function below, I expect the function call to return a string, but the value is not set at the time the function returns.
#IBAction func btnSaveSession(sender: AnyObject) {
//Prompt user to enter session name
var sessionName: String = ""
sessionName = promptUserToEnterSessionName("Save Session", message: "Please enter the name of your custom session below.");
print("session Name: " + sessionName)
//Save session to firebase.
//redirect to create session controller.
}
func promptUserToEnterSessionName(title: String, message: String) -> String{
var sessionName: String = ""
//1. Create the alert controller.
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
//2. Add the text field. You can configure it however you need.
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.text = "Enter session name."
})
//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
print("Text field: \(textField.text)")
sessionName = textField.text!
}))
// 4. Present the alert.
self.presentViewController(alert, animated: true, completion: nil)
return sessionName
}
If anyone is having problems understanding my question, please comment and I will do my best to explain.
your ok action handler(closure) will call when you click on ok button. so instead of assign value to local variable sessionName , make a sessionName variable in class.
class YourClass {
var sessionName: String?
...
#IBAction func btnSaveSession(sender: AnyObject) {
//Prompt user to enter session name
promptUserToEnterSessionName("Save Session", message: "Please enter the name of your custom session below.");
}
func promptUserToEnterSessionName(title: String, message: String) {
//1. Create the alert controller.
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
//2. Add the text field. You can configure it however you need.
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.text = "Enter session name."
})
//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
print("Text field: \(textField.text)")
self.sessionName = textField.text!
self.testDemo()
}))
// 4. Present the alert.
self.presentViewController(alert, animated: true, completion: nil)
}
fun TestDemo() {
print("session Name: " + self.sessionName)
//Save session to firebase.
//redirect to create session controller.
}
}
You can use a Callback to get the value, like this:
func AlertPrompt(vista:UIViewController, titulo:String , mensaje:String, aceptar:String, completion: #escaping (_ resultado: String)->()) {
let miAlerta = UIAlertController(title: titulo, message: mensaje, preferredStyle: UIAlertControllerStyle.alert)
miAlerta.addTextField(configurationHandler: {
(textField) -> Void in
})
let okBoton = UIAlertAction(title: aceptar, style: UIAlertActionStyle.default, handler: {
(action) -> Void in
let result = miAlerta.textFields![0] as UITextField
// You return the value here
completion(result.text! as String)
})
miAlerta.addAction(okBoton)
let cancelBoton = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil)
miAlerta.addAction(cancelBoton)
vista.present(miAlerta, animated: true, completion: nil)
}
And call the function like this:
AlertPrompt(vista: self, titulo: "Title", mensaje: "some message", aceptar: "Acept"){(resultado)->() in
print(resultado)
}
That works perfect for me, hope this help
This code:
let textField = alert.textFields![0] as UITextField
print("Text field: \(textField.text)")
sessionName = textField.text!
will be called only at the moment the button corresponding to the defined action is clicked. So at the moment of creation the UIAlertController it's not called.
Read about blocks in Swift, it should be clearer then.

AlertView with textbox, how to gather the data from entered textbox

Would like to gather the data from a AlertView out from the textbox. What I've found in the www is this:
#IBAction func showAlertTapped(sender: AnyObject) {
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Add User", message: "Enter username", 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 saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
//Do some other stuff
}
actionSheetController.addAction(saveAction)
//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)
}
Im not aware of the functionality of the AlertController, so I don't know where to bring the entred text to a e.g. simple variable?
Looks like UIAlertController has a textFields property and since you only have one text field you should be able to access it in the following way
let textField = actionSheetController.textFields?.first as UITextField
You can access the textfields string by assigning it to a local variable in the configuration handler.
//Create a local variable
var alertTextField:UITextField?
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Add User", message: "Enter username", 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 add save action
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
//Unwrap your local variable and access your textfield
if let textField = alertTextField {
println("\(textField.text)")
}
}
actionSheetController.addAction(saveAction)
//Add a text field
actionSheetController.addTextFieldWithConfigurationHandler { textField -> Void in
//TextField configuration
textField.textColor = UIColor.blueColor()
//Assign your UIAlertController textField to your local variable
alertTextField = textField
}
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
Swift 3 version:
//Create a local variable
var alertTextField:UITextField?
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Add User", message: "Enter username", 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 add save action
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .default) { action -> Void in
//Unwrap your local variable and access your textfield
if let textField = alertTextField {
print(textField.text!)
}
}
actionSheetController.addAction(saveAction)
//Add a text field
actionSheetController.addTextField { textField -> Void in
//TextField configuration
textField.textColor = UIColor.blue
//Assign your UIAlertController textField to your local variable
alertTextField = textField
}
//Present the AlertController
self.present(actionSheetController, animated: true, completion: nil)