How do I observe user editing a textField in an UIAlertController? - swift

I have an UIAlertController with a textField. Since I can't create an outlet for the textField I have to find another way to get ahold of a function that observes user input in realtime.
I have made some attempts, but not successfully. This thread explain how to add a target to the textField:
How do I check when a UITextField changes?
textfield.addTarget(self, action: #selector(ViewControllerr.textFieldDidChange(_:)), for: UIControl.Event.editingChanged)
I tried this, but don't quite understand the #selector input. Does it want my UIViewController? or my UIAlertController I tried both but got the same error message:
Value of type 'UIViewController' has no member 'textFieldDidChange'
Same with UIAlertController.
I also tried setting my textField to delegate to access functions of that class:
textField.delegate = self as? UITextFieldDelegate
But I guess this isn't the correct approach as I still couldn't access any functions except by doing:
textField.delegate?.textFieldDidBeginEditing?(textField)
But that didn't give me anything either.
I also tried adding an extension like this:
extension UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
print("🦋EDITING")
}// became first responder
func textFieldDidEndEditing(_ textField: UITextField) {
print("🦋EDITING")
}
}
But for some reason I can't access them through textView
If I extend the class like this:
extension PickNumberViewController: UITextFieldDelegate {...}
I get the same error message
Value of type 'UITextField' has no member 'textFieldDel...'
which makes no sense since I set textField to UITextFieldDelegate not UITextField.
Let me know if you would like to see my code as well.

You can try to get a reference to it with self.textF = alert.textFields?[0]
class ViewController: UIViewController {
var textF:UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
//1. Create the alert controller.
let alert = UIAlertController(title: "Some Title", message: "Enter a text", preferredStyle: .alert)
alert.addTextField { (textField) in
textField.text = "Some default text"
}
let tex = alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak alert] (_) in
}))
// 4. Present the alert.
self.present(alert, animated: true, completion: nil)
self.textF = alert.textFields?[0]
self.textF.addTarget(self, action: #selector(self.textFieldDidChange), for: UIControl.Event.editingChanged)
}
}
#objc func textFieldDidChange() {
print(textF.text)
}
}

You don't need to hold a reference to the text field. You can add target or set delegate within the addTextField closure like this.
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alert.addTextField { textField in
textField.delegate = self
textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged)
textField.placeholder = "Enter name"
}
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
#objc func textChanged(_ sender: UITextField) {
print("Text changed - ", sender.text!)
}
}
extension ViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
print("EDITING")
}
func textFieldDidEndEditing(_ textField: UITextField) {
print("EDITING")
}
}

iOS13+ One option is to use UIAction
extension UIAlertController {
public func addTextField(_ configuration: #escaping ((UITextField) -> Void),
onEditingDidBegin: ((UITextField) -> Void)? = nil,
onEditingChanged: ((UITextField) -> Void)? = nil,
onEditingDidEnd: ((UITextField) -> Void)? = nil) {
self.addTextField { textField in
configuration(textField)
if let onEditingDidBegin {
textField.addAction(UIAction { action in
onEditingDidBegin(action.sender as! UITextField)
}, for: .editingDidBegin)
}
if let onEditingChanged {
textField.addAction( UIAction { action in
onEditingChanged(action.sender as! UITextField)
}, for: .editingChanged)
}
if let onEditingDidEnd {
textField.addAction( UIAction { action in
onEditingDidEnd(action.sender as! UITextField)
}, for: .editingDidEnd)
}
}
}
Use like so:
let alert = UIAlertController(title: "Enter Text", message: nil, preferredStyle: .alert)
alert.addTextField { textField in
textField.text = "Some text"
textField.placeholder = "Placeholder"
} onEditingDidBegin: { textField in
textField.selectAll(nil) // selects all text
} onEditingChanged: { textField in
if let text = textField.text {
textField.textColor = text.count > 5 ? .red : .label
}
} onEditingDidEnd: { textField in
if let text = textField.text {
if text.count > 5 {
textField.text = "This is too long"
}
}
}
You shouldn't use onEditingDidEnd for your final validation though. Put that in the UIAlertAction for your "OK" button and pull out the textFields:
let okAction = UIAlertAction(title: "OK", style: .destructive) { [weak self, weak alert] _ in
if let self,
let alert,
let textField = alert.textFields?.first {
// validate (here, 'self', is a view controller)
self.label.text = textField.text
}
}
alert.addAction(okAction)

Related

How to make actionsheet pop over a bar button on iPhone

I know how to make an action sheet pop over a button on iPad but not for iPhone. it seems that on iphone the action sheet doesn't have popoverPresentationController:
#IBAction func addChannel(_ sender: Any) {
let sender = sender as? UIBarButtonItem
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let addChanel = UIAlertAction(title: "Add a Channel", style: .default) { (_) in
self.addChannel()
}
let addContact = UIAlertAction(title: "Add a Contact", style: .default){ _ in
self.addContact()
}
actionSheet.addAction(addChanel)
actionSheet.addAction(addContact)
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
// actionSheet.popoverPresentationControlle
if let popover = actionSheet.popoverPresentationController{
actionSheet.popoverPresentationController?.barButtonItem = sender
actionSheet.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
}
self.present(actionSheet, animated: true)
}
I want the action sheet pop over a button just like what WeChat + button does. Other stackoverflow answers are too old and not workable
Any idea is welcome!
Source.
You cannot use UIAlertController as a popover on iPhones. One of alternatives is using a UITableView or something inside new UIViewController, which you can create like described below (Swift 5.0).
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet private weak var button: UIButton!
#IBAction func buttonAction() {
let vc = UIViewController()
vc.view.backgroundColor = UIColor.blue
vc.preferredContentSize = CGSize(width: 200, height: 200)
vc.modalPresentationStyle = .popover
let ppc = vc.popoverPresentationController
ppc?.permittedArrowDirections = .any
ppc?.delegate = self
ppc?.sourceView = button
present(vc, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Also, popoverPresentationController has property called barButtonItem which you can assign to your nav bar button.

How to set textfield placeholder's value to time picker's selected value

On "func can()" pop up shows a textfield on which we click & a time picker shows.I want to set textfield placeholder value to that selected picker's value
#IBAction func can(_ sender: UIButton) {
let alertController : UIAlertController = UIAlertController(title: "", message: "Confirm Order ?", preferredStyle: UIAlertController.Style.alert)
alertController.addTextField
{
(textField : UITextField!) -> Void in
textField.placeholder = "Expected Time"
textField.addTarget(self, action: #selector(self.myTargetFunction), for: .touchDown)
let okAction = UIAlertAction(title: "CANCEL", style: UIAlertAction.Style.destructive) {
(UIAlertAction) in
}
let CANAction = UIAlertAction(title: "SUBMIT", style: UIAlertAction.Style.default) {
(UIAlertAction) in
// self.actionCancelOrderApiCall()
}
alertController.addAction(okAction)
alertController.addAction(CANAction)
self.present(alertController, animated: true, completion: nil)
}
}
#objc func myTargetFunction(textField: UITextField) {
var datePicker : UIDatePicker = UIDatePicker(frame: CGRect(x: 0,y: 500, width: self.view.frame.size.width, height: 250))
datePicker.datePickerMode = .time
datePicker.backgroundColor = UIColor.white
if datePicker.isHidden == false{
tblV.isHidden = true
atchment1.isHidden = true
}
self.view.addSubview(datePicker)
}
The below changes should make it work
textField.addTarget(self, action: #selector(self.myTargetFunction(sender:)), for: .touchDown)
change your method signature to
#objc func myTargetFunction(sender: UITextField)
inside myTargetFunction(),
sender.placeholderText = datePicker.date

Save Alert-textfieldinput in Swift

I want to have a table view, where you can add a new element when you click on the last cell. I can't find out, how I save whats put in the textfield, after you click "ok".
As you can probably see from my code, I'm very new to programming and it is likely way to complicated. It would be super cool, if someone could help me out.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var companyTableView: UITableView!
var psgTextField: UITextField?
var emTextField: UITextField?
var passengers = [""]
var button: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
configureButton()
companyTableView.delegate = self
companyTableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return passengers.count
}
#objc func buttonTapped(sender: UITapGestureRecognizer) {
let alertController = UIAlertController(title: "Enter name and email",
message: nil,
preferredStyle: .alert)
alertController.addTextField(configurationHandler: psgTextField)
alertController.addTextField(configurationHandler: emTextField)
let okAction = UIAlertAction(title: "Submit", style: .default, handler: self.okHandler)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(okAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true)
}
func psgTextField(textField: UITextField!) {
psgTextField = textField
psgTextField?.placeholder = "Name"
}
func emTextField(textField: UITextField!) {
emTextField = textField
emTextField?.placeholder = "Email"
}
func okHandler(alert: UIAlertAction!) {
let psgStr = (psgTextField?.text)!
passengers.removeLast()
passengers.append(psgStr)
passengers.append("")
}
func configureButton() {
button = UILabel()
let frame = CGRect(x: 0, y: 5, width: 300, height: 30)
button?.frame = frame
button?.text = " Add Passenger"
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(buttonTapped(sender:)))
button?.addGestureRecognizer(tapGesture)
button?.isUserInteractionEnabled = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let passenger = passengers[indexPath.row]
if let cell = companyTableView.dequeueReusableCell(withIdentifier: "cell") {
cell.textLabel?.text = passenger
if cell.textLabel?.text == "" {
cell.contentView.addSubview(button!)
} else {
// Nothing
}
return cell
}
return UITableViewCell()
}
}
When I now click on "Submit", the alert just goes of and nothing changed.
You need to call companyTableView.reloadData() in your okHandler method.

How to make the cancel button in alert cancel the action?

I made an alert as you can see, but the app adds the item, no matter if I'm clicking the "NO" button og the "Yes, I'm sure" button when the alert pops up.
My goal is to make the "NO" action, cancel the action so the input won't be added anyway. Can you tell me how to?
import UIKit
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var input: UITextField!
#IBAction func addItem(_ sender: Any)
{
createAlert(title: "That's a good grail!", message: "Are you sure you want to add this grail?")
if (input.text != "")
{
list.append(input.text!)
input.text = ""
}
}
override func viewDidLoad()
{
super.viewDidLoad()
self.input.delegate = self
}
//HIDE KEYBOARD:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//PRESSES RETURN KEY:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
input.resignFirstResponder()
return true
}
func createAlert (title:String, message:String)
{
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
//CREATING OK BUTTON
let OKAction = UIAlertAction(title: "Yes, I'm sure!", style: .default) { (action:UIAlertAction!) in
// Code in this block will trigger when OK button tapped.
print("Ok button tapped");
}
alertController.addAction(OKAction)
// Create Cancel button
let cancelAction = UIAlertAction(title: "No!", style: .cancel) { (action:UIAlertAction!) in
print("Cancel button tapped");
}
alertController.addAction(cancelAction)
// Present Dialog message
self.present(alertController, animated: true, completion:nil)
}
}
EDIT:
The code looks like this now, thanks:
import UIKit
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var input: UITextField!
#IBAction func addItem(_ sender: Any)
{
createAlert(title: "That's a good grail!", message: "Are you sure you want to add this grail?")
}
override func viewDidLoad()
{
super.viewDidLoad()
self.input.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//HIDE KEYBOARD:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//PRESSES RETURN KEY:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
input.resignFirstResponder()
return true
}
func createAlert (title:String, message:String)
{
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
//CREATING OK BUTTON
let OKAction = UIAlertAction(title: "Yes, I'm sure!", style: .default) { (action:UIAlertAction!) in
// Code in this block will trigger when OK button tapped.
print("Ok button tapped");
if (self.self.input.text != "")
{
list.append(self.input.text!)
self.input.text = ""
}
}
alertController.addAction(OKAction)
// Create Cancel button
let cancelAction = UIAlertAction(title: "No!", style: .cancel) { (action:UIAlertAction!) in
print("Cancel button tapped");
}
alertController.addAction(cancelAction)
// Present Dialog message
self.present(alertController, animated: true, completion:nil)
}
}
simply just put the code for adding the item to the OK closure:
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var input: UITextField!
#IBAction func addItem(_ sender: Any)
{
createAlert(title: "That's a good grail!", message: "Are you sure you want to add this grail?")
}
override func viewDidLoad()
{
super.viewDidLoad()
self.input.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//HIDE KEYBOARD:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//PRESSES RETURN KEY:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
input.resignFirstResponder()
return true
}
func createAlert (title:String, message:String)
{
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
//CREATING OK BUTTON
let OKAction = UIAlertAction(title: "Yes, I'm sure!", style: .default) { (action:UIAlertAction!) in
// Code in this block will trigger when OK button tapped.
if (input.text != "")
{
list.append(input.text!)
input.text = ""
}
print("Ok button tapped");
}
alertController.addAction(OKAction)
// Create Cancel button
let cancelAction = UIAlertAction(title: "No!", style: .cancel) { (action:UIAlertAction!) in
print("Cancel button tapped");
}
alertController.addAction(cancelAction)
// Present Dialog message
self.present(alertController, animated: true, completion:nil)
}
}
You're adding the input.text anyway in your addItem(_ :) method after you show your UIAlertController.
So if you want to avoid the input.text is added always and only when the OK button is tapped you should include in the closure for the action when you created it and remove it after the presentation of the UIAlertController
let OKAction = UIAlertAction(title: "Yes, I'm sure!", style: .default) { [weak self] _ in
guard let selfStrong = self else {
return
}
// Code in this block will trigger when OK button tapped.
if (selfStrong.input.text != "") {
selfStrong.list.append(input.text!)
selfStrong.input.text = ""
}
}
For the cancel button, you don't need any action closure unless you want to do something when the cancel button is tapped too.
I hope this helps you.
You are not appending the item on click of "Yes, I'm sure" button. Remove the below code inside #IBAction func addItem(_ sender: Any) method and put it inside OKAction handler block.
if (input.text != "")
{
list.append(input.text!)
input.text = ""
}
Do like this:
#IBAction func addItem(_ sender: Any)
{
createAlert(title: "That's a good grail!", message: "Are you sure you want to add this grail?")
}
Inside method: func createAlert (title:String, message:String) (put append code here)
let OKAction = UIAlertAction(title: "Yes, I'm sure!", style: .default) { (action:UIAlertAction!) in
// Code in this block will trigger when OK button tapped.
print("Ok button tapped");
if (input.text != "")
{
list.append(input.text!)
input.text = ""
}
}

Why are the buttons on my UIAlert duplicating?

I'm attempting to build an app using Swift that's similar to the Notes app, but I'd like to have an Alert pop up when the user presses the "+" button to add a new Note - and the Alert would prompt the user to enter a name. The name is then added as a new row in the underlying table. So far, I've been able to do this, but every time i click the "+" sign, the buttons I've added to the Alert get re-added. In other words, the Alert should have an "OK" & a "Cancel" button. But on the 2nd time the Alert pops up, there are 2 "OK" buttons, and 2 "Cancel" buttons, and so on. Any ideas how i can fix this? Thanks for your help!
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
var data:[String] = []
//create alert controller
var alert = UIAlertController(title: "Name", message: "Enter a name", preferredStyle: .alert)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.title = "Notes"
//create "add" button
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addAlert))
self.navigationItem.rightBarButtonItem = addButton
//create edit button so we can delete rows
self.navigationItem.leftBarButtonItem = editButtonItem
load()
}
//function to add new note. This code is executed when the "Add" button is clicked
func addNote()
{
//code to prevent being able to add rows while in edit mode
if (table.isEditing) {
return
}
let textField = self.alert.textFields![0] as UITextField
let name:String = textField.text!
data.insert(name, at: 0)
let indexPath:IndexPath = IndexPath(row: 0, section: 0)
table.insertRows(at: [indexPath], with: .automatic)
save()
}
func addAlert()
{
alert.addTextField(configurationHandler: { (textField) -> Void in
textField.placeholder = "Enter Name"})
self.present(alert, animated: true, completion: nil)
//grab value from the text field and print when user clicks OK
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (UIAlertAction) in
let textField = self.alert.textFields![0] as UITextField
print("Text Field: \(textField.text!)")
self.addNote()
}))
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = data[indexPath.row]
return cell
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
//puts table in edit mode
table.setEditing(editing, animated: animated)
}
//function to delete rows from table
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
data.remove(at: indexPath.row)
table.deleteRows(at: [indexPath], with: .fade)
save()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "detail", sender: nil)
}
func save()
{
UserDefaults.standard.set(data, forKey: "notes")
UserDefaults.standard.synchronize()
}
func load()
{
if let loadedData = UserDefaults.standard.value(forKey: "notes") as? [String]
{
data = loadedData
table.reloadData()
}
}
Put alert.addAction before presenting the alert. Try this code in addAlert() method
func addAlert()
{
var alert = UIAlertController(title: "Name", message: "Enter a name", preferredStyle: .alert)
alert.addTextField(configurationHandler: { (textField) -> Void in
textField.placeholder = "Enter Name"})
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (UIAlertAction) in
let textField = self.alert.textFields![0] as UITextField
print("Text Field: \(textField.text!)")
self.addNote()
}))
self.present(alert, animated: true, completion: nil)
}
You have to present the alert only after adding the actions(Buttons).
func addAlert()
{
alert.addTextField(configurationHandler: { (textField) -> Void in
textField.placeholder = "Enter Name"})
//grab value from the text field and print when user clicks OK
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (UIAlertAction) in
let textField = self.alert.textFields![0] as UITextField
print("Text Field: \(textField.text!)")
self.addNote()
}))
self.present(alert, animated: true, completion: nil)
}