How to check and enable Action in a UIAlertController only if the textfield is not empty? - swift

I'm trying to figure out how to keep the Action Button disabled until the user enters some text in, at which point the button would be enabled again. I've been searching around, and some people are suggesting to use Observers? Would that be the best way to go?
Cheers!
#objc func addExercise() {
var textField = UITextField()
let alert = UIAlertController(title: "New Exercise", message: "Please name your Exercise...", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .default) { (UIAlertAction) in
alert.dismiss(animated: true, completion: nil)
}
let addAction = UIAlertAction(title: "Add Exercise", style: .default) { (UIAlertAction) in
//Add Exercise to database.
//Append exercise to selected workout object.
let exercise = Exercises()
exercise.exerciseName = textField.text!
try! self.realm.write {
self.selectedWorkout?.exercise.append(exercise)
self.loadExercises()
}
}
alert.addTextField { (alertTextField1) in
alertTextField1.delegate = self
alertTextField1.placeholder = "Bench Press"
alertTextField1.text = textField.text
textField = alertTextField1
}
alert.addAction(addAction)
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}

Using notification is a way but long work instead you can use simple delegate method or action method of textfield which is much easier as follow:
weak var buttonActionToEnable: UIAlertAction?
alert.addTextField { (alertTextField1) in
alertTextField1.delegate = self
alertTextField1.placeholder = "Bench Press"
alertTextField1.text = textField.text
alertTextField1.addTarget(self, action: #selector(self.textFieldChanged), for: .editingChanged)
}
self.buttonActionToEnable = addAction
addAction.isEnabled = false
#objc func textFieldChanged(_ sender: Any) {
let textfield = sender as UITextField
self.buttonActionToEnable?.isEnabled = textfield.text.count > 0
}

Related

UIAlertController not getting deallocated

I have created a UIAlertController and added a text field into it. The problem is that the after I dismiss the alertcontroller, it is not deallocating and shows in the memory graph.
let alertController = UIAlertController(title: "Enter link", message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.placeholder = "www.xxx.com"
}
let cancel = UIAlertAction(title: "CANCEL", style: .cancel, handler: nil)
let ok = UIAlertAction(title: "SUBMIT", style: .default) { [weak self] (_) in
guard let self = self else { return }
guard let textFields = alertController.textFields, let textField = textFields.first else { return }
if let text = textField.text {
}
}
alertController.addAction(cancel)
alertController.addAction(ok)
present(alertController, animated: true, completion: nil)
However, if I remove the alertController.addTextField then it gets deallocated and it does not exist in the memory graph.
I tried checking if there are any memory leaks introduced due to my code but found none.
You're retaining the alertController inside of "ok" alert action, which is in turn retained by UIAlertController.
That's a retain cycle, neither of objects will be able to release each other.
You should avoid that, perhaps by using a weak reference, i.e:
let ok = UIAlertAction(title: "SUBMIT", style: .default) { [weak alertController] (_) in
// Upgrade weak to strong reference
guard let alertController = alertController else { return }
// Do your thing
}

Make a network call when textfield has entered

I have an alertview with textfield, and would like to make call if a user enters something in the text field and confirm it. I am confused how to handle this workflow.
func btnTapped(cell: UICollectionViewCell) {
let indexPath = self.collectionView.indexPath(for: cell)
openAlert()
// I want to move the following part once user enters textfield in the alertview
fetchData(urlString: url, data: data[indexPath.row]) { [weak self] (response, error) in
if error == nil
{
DispatchQueue.main.async {
//do any ui update here!
self?.collectionView.reloadData()
}
}
}
}
func openAlert(){
let alertController = UIAlertController(title: "Alert", message: "", preferredStyle: .alert)
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Enter reason"
}
let saveAction = UIAlertAction(title: "Confirm", style: .default, handler: { alert -> Void in
if let textField = alertController.textFields?[0] {
if textField.text!.count > 0 {
print("Text :: \(textField.text ?? "")")
}
}
})
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: {
(action : UIAlertAction!) -> Void in })
alertController.addAction(cancelAction)
alertController.addAction(saveAction)
alertController.preferredAction = saveAction
self.present(alertController, animated: true, completion: nil)
}
You need to work with the text field delegates and use textFieldDidChange, then anytime the text is changed on the alert, call the API. Here is a quick example:
func textFieldDidChange(_ textField: UITextField) {
// Make API call here
}
Then in the completion block for the API call, set whatever data you would like depending on if the API confirmed or did not confirm.

my segue is not working

I have a button that if user click on it it must show another vc and it's working properly until I add UIAlertController which it's getting data from user and after user click done on alertaction nothing happens
this is my code:
#IBAction func DfsClicked(_ sender: AnyObject) {
let alertController = UIAlertController(title: "DFS?", message: "Please input DFS depth:", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Confirm", style: .default) { (_) in
if let field = alertController.textFields?[0] {
// store your data
self.depth = Int(field.text!)!
print(self.depth)
Puzzle.AnswerNode = dfs(inputdepth: self.depth,SortedPuzzle:self.SortedPuzzle)
} else {
// user did not fill field
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
alertController.addTextField { (textField) in
textField.placeholder = "Default is 2"
textField.keyboardType = UIKeyboardType.numberPad
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true)
}
Use this method to perform segue, inside confirm action block
self.performSegue(withIdentifier: <Identifier>, sender: <Any?>)

How do we create and dismiss an UIAlertController without user input? (Swift)

I've been looking up a lot of tutorials on UIAlertController. Thus far, the way I found was to activate a UIAlertController by linking it to a button or label and then call a IBAction.
I tried to replicate the code to automatically pop an alert when user enters the app (I wanted to ask the user if they want to go through the tutorial). However, I keep getting the error:
Warning: Attempt to present UIAlertController on MainViewController whose view is not in the window hierarchy!
Then I tried to add the UIAlertController to the MainViewController via addChildViewController and addSubview. However, I get the error:
Application tried to present modally an active controller
I figured that I cannot use the presentViewController function and commented it out.
The UIAlertController is displayed BUT when I tried to click on the cancel or the never button, this error occurs.
Trying to dismiss UIAlertController with unknown presenter.
I am really stumped. Can someone share what I am doing wrong? Thank you so much. Here is the code.
func displayTutorial() {
alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
self.addChildViewController(alertController)
self.view.addSubview(alertController.view)
alertController.didMoveToParentViewController(self)
alertController.view.frame.origin.x = self.view.frame.midX
alertController.view.frame.origin.y = self.view.frame.midY
//alertController.popoverPresentationController?.sourceView = self.view*/
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Destructive) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
//self.presentViewController(alertController, animated: false) {}
}
I found the solution. Apparently, I cannot call the UIAlertController from the func viewDidLoad. I must call the function from viewDidAppear. So my code now is
override func viewDidAppear(animated: Bool) {
if tutorial == 0 {
displayTutorial(self.view)
}
}
func displayTutorial(sender:AnyObject) {
let alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Default) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
self.presentViewController(alertController, animated: true, completion: nil)
if let pop = alertController.popoverPresentationController {
let v = sender as UIView
pop.sourceView = view
pop.sourceRect = v.bounds
}
}
Thanks to this posting: Warning: Attempt to present * on * whose view is not in the window hierarchy - swift
Below UIAlertController with extension would help you show alert with dynamic number of buttons with completion handler for selected index
extension UIViewController {
func displayAlertWith(message:String) {
displayAlertWith(message: message, buttons: ["Dismiss"]) { (index) in
}
}
func displayAlertWith(message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
displayAlertWithTitleFromVC(vc: self, title: Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String, andMessage: message, buttons: buttons, completion: completion)
}
func displayAlertWithTitleFromVC(vc:UIViewController, title:String, andMessage message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
for index in 0..<buttons.count {
let action = UIAlertAction(title: buttons[index], style: .default, handler: {
(alert: UIAlertAction!) in
if(completion != nil){
completion(index)
}
})
alertController.addAction(action)
}
DispatchQueue.main.async {
vc.present(alertController, animated: true, completion: nil)
}
}
}
If you need to auto dismiss the alert you can call dismiss on presented view controller after some delay.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
vc.dismiss(animated: true, completion: nil)
}
Hope this might help you.

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)