I have programmatically added UITextView's and a class object that gets some part of it changed upon calling:
textViewShouldEndEditing(textView: UITextView)
I handle saving the object fields for the user as a batch save, but the problem is that the last edited UITextView value doesn't ever make it to the save, even though the textViewShouldEndEditing function gets called. The only other complication is that the batch saving is part of a confirmation message via UIAlertAction.
To clarify, the class object that the UIAlertAction accesses appears to be out of date even though all functions are called in the correct order. I hope this is clear.
var tmpGuest:VIG = VIG()
func textViewShouldEndEditing(textView: UITextView) -> Bool {
let saveTxt:String = (textView.text == "--" ? "" : textView.text)
//SET SOME tmpGuest VALUES HERE
return true
}
#IBAction func editInfo(sender: AnyObject) {
let actionSheetController: UIAlertController = UIAlertController(title: "Are you sure you wish to save?", message: "Changes are permanent.", preferredStyle: .Alert)
let okAction: UIAlertAction = UIAlertAction(title: "Yes", style: .Default) { action -> Void in
//SAVE
self.setGuest.setRecords(guestMainTmp: self.tmpGuest)
}
actionSheetController.addAction(okAction)
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
Related
I encountered a strange issue and maybe it is only a lack of knowledge about Swift 3.0 / iOS 10 so hopefully you can guide me into the right direction or explain to me what I'm doing wrong.
HOW IT CURRENTLY WORKS
I am trying to create a UIAlertController with style .alert so I can get a user text input for my app. My requirements are that the text must not be empty and if there was a text there before, it must be different.
I can use the following code to achieve what I want:
//This function gets called by a UIAlertController of style .actionSheet
func renameDevice(action: UIAlertAction) {
//The AlertController
let alertController = UIAlertController(title: "Enter Name",
message: "Please enter the new name for this device.",
preferredStyle: .alert)
//The cancel button
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
//The confirm button. Make sure to deactivate on first start
let confirmAction = UIAlertAction(title: "Ok", style: .default, handler: { action in
self.renameDevice(newName: alertController.textFields?.first?.text)
})
//Configure the user input UITextField
alertController.addTextField { textField in
log.debug("Setting up AlertDialog target")
textField.placeholder = "Enter Name"
textField.text = self.device.getName()
textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged)
}
//Disable the OK button so that the user first has to change the text
confirmAction.isEnabled = false
self.confirmAction = confirmAction
//Add the actions to the AlertController
alertController.addAction(cancelAction)
alertController.addAction(confirmAction)
present(alertController, animated: true, completion: nil)
}
var confirmAction: UIAlertAction?
func textFieldDidChange(_ textField: UITextField){
log.debug("IT CHAGNED!=!=!=!=!")
if let text = textField.text {
if !text.isEmpty && text != self.device.getName() {
confirmAction?.isEnabled = true
return
}
}
confirmAction?.isEnabled = false
}
//Finally this code gets executed if the OK button was pressed
func renameDevice(newName: String?){ ... }
HOW I WANT IT TO WORK
So far so good but I'm going to ask the user for a text input at various places so I want to use a utility class to handle all this stuff for me. The final call shall look like this:
func renameDevice(action: UIAlertAction) {
MyPopUp().presentTextDialog(title: "Enter Name",
message: "Please enter the new name for this device.",
placeholder: "New Name",
previousText: self.device.getName(),
confirmButton: "Rename",
cancelButton: "Cancel",
viewController: self){ input: String in
//do something with the input, e. g. call self.renameDevice(newName: input)
}
WHAT I CAME UP WITH
So I implemented everything in this little class:
class MyPopUp: NSObject {
var confirmAction: UIAlertAction!
var previousText: String?
var textField: UITextField?
func presentTextDialog(title: String, message: String?, placeholder: String?, previousText: String?, confirmButton: String, cancelButton: String, viewController: UIViewController, handler: ((String?) -> Swift.Void)? = nil) {
//The AlertController
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
//The cancel button
let cancelAction = UIAlertAction(title: cancelButton, style: .cancel)
//The confirm button. Make sure to deactivate on first start
confirmAction = UIAlertAction(title: confirmButton, style: .default, handler: { action in
handler?(alertController.textFields?.first?.text)
})
//Configure the user input UITextField
alertController.addTextField { textField in
log.debug("Setting up AlertDialog target")
self.textField = textField
}
//Set placeholder if necessary
if let placeholder = placeholder {
self.textField?.placeholder = placeholder
}
//Set original text if necessary
if let previousText = previousText {
self.textField?.text = previousText
}
//Set the target for our textfield
self.textField?.addTarget(self, action: #selector(textChanged), for: .editingChanged)
log.debug("It appears that our textfield \(self.textField) has targets: \(self.textField?.allTargets)")
//Store the original text for a later comparison with the new entered text
self.previousText = previousText
//Disable the OK button so that the user first has to change the text
confirmAction.isEnabled = false
//Add the actions to the AlertController
alertController.addAction(cancelAction)
alertController.addAction(confirmAction)
viewController.present(alertController, animated: true, completion: nil)
}
func textChanged() {
if let text = textField?.text {
if !text.isEmpty && text != previousText {
confirmAction.isEnabled = true
return
}
}
confirmAction.isEnabled = false
}
}
THE PROBLEM
My problem is that no matter where I try to set the target for the UITextField of the UIAlertController, it never executes my target. I tried setting the TextFields delegate in alertController.addTextField{} as well as setting the target there. The issue which confuses me the most is that setting the placeholder and original text works just fine but delegate or target functions are never called. Why does the same code works when executed in a UIViewController but does not work when executed in a utility class?
THE SOLUTION (UPDATE)
Apparently I made a mistake. In my viewcontroller, I create an instance of MyPopUp and call the present() function on it.
MyPopUp().presentTextDialog(title: "Enter Name",
message: "Please enter the new name for this device.",
placeholder: "New Name",
previousText: self.device.getName(),
confirmButton: "Rename",
cancelButton: "Cancel",
viewController: self)
In the presentTextDialog() I thought setting the current instance of MyPopUp as the delegate/target would be enough but it seems that the MyPopUp instance is released immediately and therefore never called. My very simple workaround is to create the MyPopUp instance in an instance variable and call the present method whenever I need to.
let popup = MyPopUp()
func renameDevice(action: UIAlertAction) {
popup.presentTextDialog(...){ userinput in
renameDevice(newName: userinput)
}
}
Okay so here's exactly what I did wrong.
I created a utility class which I had to instantiate
The class itself was basically empty and it's only purpose was to be the target or delegate of the UITextField
I instantiated the class and immediately called the presentation function without keeping a reference around
By not keeping a reference to my instance, the object must have gotten released immediately after presenting the UIAlertController in my viewcontroller.
Solution: Just keep a reference around in your viewcontroller. Of course a local variable won't do. I store the reference in an instance variable of my viewcontroller but this doesn't feel very "swifty". I'm still a beginner in swift and maybe my mind is "damaged" by other languages (java, c#). I will probably start by making my utility class a singleton or creating an extension for UIViewController to present the alert. If you have other good ideas feel free to teach them to me :)
Instead of presenting dialogue in NSObject class. You must use delegates and protocol to present an alert. Replace your code with this. In View Controller we have a function named renameDevice. You can present alert here.
MyPopUp.swift
import UIKit
class MyPopUp: NSObject {
var confirmAction: UIAlertAction!
var previousText: String?
var textField: UITextField?
var delegate : MyPopUpDelegate!
func textChanged() {
if let text = textField?.text {
if !text.isEmpty && text != previousText {
confirmAction.isEnabled = true
return
}
}
confirmAction.isEnabled = false
}
}
protocol MyPopUpDelegate {
func renameDevice(action: UIAlertAction)
}
ViewController.swift
import UIKit
class ViewController: UIViewController,MyPopUpDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func renameDevice(action: UIAlertAction) {
// Add dialogue that you wnat to create.
}
}
In your MyPopUp class first you need to conform to UITextFieldDelegate method like this
class MyPopUp:NSObject,UITextFieldDelegate {
then while adding your UITextField to alert you need to set delegate to that UITextField like this
alertController.addTextField { textField in
log.debug("Setting up AlertDialog target")
textField.delegate = self
self.textField = textField
}
then you need to implement UITextField Delegate method to get the change in your UITextField like this
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
This will solve your problem.Also check this
Alright, I've looked up a lot of documentation on how the UIAlertController's work and I have a rather unique scenario. My application is being designed to work with a HID Bluetooth scanner. When I use:
preferredStyle: UIAlertControllerStyle.Alert
After I generate an alert that the item I have scanned is incorrect. Cool, alert is happening, problem is if I scan again ( which emulates keyboard input ), the return key is being sent to the alert and the alert is running the dismiss action.
If I use:
preferredStyle: UIAlertControllerStyle.ActionSheet
Then the alert is staying where it should be and ignoring scans while the alert window is up just how I want it.
So my question is, how do I capture the return key and prevent the Alert from calling the dismiss action? I'm a bit new with swift and I have a feeling the answer is simple, but I've tried half a dozen things that just isn't working.
If there is a setting to prevent all user input to the alert window or anything solution, I'm all for any method. I just rather not use the ActionSheet, and prefer to use the iOS alerts instead of creating my own screen. If this is not possible, I'm sure I can build my own 'alerts' window.
Code, that I'm calling from a simple Alerts class I made.
import UIKit
class Alerts {
var controller: UIViewController
var message: String
var title: String
init?(title: String, message: String, controller: UIViewController){
if title.isEmpty || message.isEmpty {
return nil
}
self.title = title
self.message = message
self.controller = controller
}
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
}
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
}
Try something like this.
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
showAlert()
}
showAlert()
}
func showAlert() {
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
i’m using UIAlertController for asking user enter a string, and this string should not be empty. For this purpose i'm adding handler for every editing event on UITextField located on UIAlertController. This handler checks, if the textField is empty, it makes «OK» button (located on UIAlertController) disabled, and, if the textField is not empty, it makes «OK» button enabler. «OK» button also located on UIAlertController.
I have a problem with access to the «OK» button from this
handler function:
There is no opportunity to pass the button as a parameter directly to the handler function because i’m calling it using #selector.
I’ve also tried to access UIAlertController through the UITextField.superview from handler function, but it didn’t work: it returned UIView, which can’t be downcasted to UIAlertController. And it’s not clear why it is so: UITextField is subview of UIAlertController and consequently UITextField.superview must return UIAlertController.
I’ve also tried the easiest way: declare UIAlertController as property in my ViewController class: this allows me to access the button from handler function without passing it directly to function. But in this case i’ve collided with another problem: at the runtime Xcode shows this warning in debug area: Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior ()
And my question is how to get access to «OK» button from my handler function?
class TVC_ProgramsList: UITableViewController {
var enterNameAC = UIAlertController()
#IBAction func addpush(sender: UIBarButtonItem) {
addProgram()
}
func addProgram() {
enterNameAC = UIAlertController(title: "Adding program", message: "Enter program name", preferredStyle: UIAlertControllerStyle.Alert)
enterNameAC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
enterNameAC.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
enterNameAC.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Program name"
textField.addTarget(self, action: #selector(TVC_ProgramsList.enterProgramNameAC_editingTextField), forControlEvents: UIControlEvents.AllEditingEvents)
}
self.presentViewController(enterNameAC, animated: true, completion: nil)
}
func enterProgramNameAC_editingTextField() {
let programNameString = enterNameAC.textFields![0].text!
if programNameString.characters.count > 0 {
enterNameAC.actions[1].enabled = true
} else {
enterNameAC.actions[1].enabled = false
}
}
}
Why don't you make an attribute of the UIAlertAction? I've changed your code:
class TVC_ProgramsList: UITableViewController {
var enterNameAC = UIAlertController()
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)
#IBAction func addpush(sender: UIBarButtonItem) {
addProgram()
}
func addProgram() {
enterNameAC = UIAlertController(title: "Adding program", message: "Enter program name", preferredStyle: UIAlertControllerStyle.Alert)
enterNameAC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
enterNameAC.addAction(self.okAction)
enterNameAC.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Program name"
textField.addTarget(self, action: #selector(TVC_ProgramsList.enterProgramNameAC_editingTextField), forControlEvents: UIControlEvents.AllEditingEvents)
}
self.presentViewController(enterNameAC, animated: true, completion: nil)
}
func enterProgramNameAC_editingTextField() {
let programNameString = enterNameAC.textFields![0].text!
// use okAction here
self.okAction.enabled = programNameString.characters.count > 0
}
}
Passing it with addTarget won't be possible. But it's a valid approach to make a class attribute out of it and therefore making it accessible in other parts of your ViewController.
Also i found another solution of this problem: it's enough to make lazy declaration of enterNameAC to avoid alert message in debug area at runtime:
lazy var enterNameAC = UIAlertController()
But it's not all: All i said it was about Swift 2, but yesterday i updated to Swift 3, and, it's miracle: it works without any debug area messages, even without lazy declaration!
I want to query a user for his or her name in one of my apps. I'd like to have a check to make sure that the user entered a non-blank string and then re-prompt the user with an error message if the input is invalid. So far this is what I have:
while name == "" {
name = promptUserForName()
}
And the promptUserForName( ) method is this:
private func promptUserForName() -> String {
let alert = UIAlertController(title: "Enter your name:", message: nil, preferredStyle: .Alert)
// Presents a keyboard for user to enter name
var userInputField: UITextField?
alert.addTextFieldWithConfigurationHandler { (textField: UITextField!) in
userInputField = textField
}
var name: String = ""
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: {(action) -> Void in
if let userInfo = userInputField!.text {
name = userInfo
}
})
alert.addAction(defaultAction)
self.presentViewController(alert, animated: true, completion: nil)
return name
}
Unfortunately, the while loop just loops infinitely without prompting the user and waiting for a response. Is there a way to make this work, perhaps by putting the while loop within the promptUserForName( ) method? Thanks.
Don't use a while-loop. Just check whether the name is blank in the alert's completion handler. If it is blank, call promptUserForName again; otherwise do whatever you need to do. You probably shouldn't return anything from this method, because it's asynchronous; instead, pass the name into another method from within the alert's completion handler.
I am making an alert that has a couple of text fields, this will be saved on a server once save is hit. I managed to get it to function but found something odd that I didn't understand. I'm not sure how to explain it in words so I will post the code in question.
var enteredName: UITextField!
let myAlert: UIAlertController = UIAlertController(title: "", message: nil, preferredStyle: .Alert)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
println(self.enteredName.text)
}
myAlert.addAction(saveAction)
myAlert.addTextFieldWithConfigurationHandler { textField -> Void in
textField.placeholder = "Name"
self.enteredName = textField
}
self.presentViewController(myAlert, animated: true, completion: nil)
Now, the above code works, and I have no problems with it, however, I had a very similar block of code originally that did not work. The above code snippet will display an alert with a text field, after typing in something and hitting save it will print what you typed. The below code was supposed to do that but printed blank lines, I assume empty strings.
var enteredName: String!
let myAlert: UIAlertController = UIAlertController(title: "", message: nil, preferredStyle: .Alert)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
println(self.enteredName)
}
myAlert.addAction(saveAction)
myAlert.addTextFieldWithConfigurationHandler { textField -> Void in
textField.placeholder = "Name"
self.enteredName = textField.text
}
self.presentViewController(myAlert, animated: true, completion: nil)
As you can see, not much has been changed, and to me, both code blocks look like they should run. The differences: In the code snippet that did not work
The variable is a String instead of a UITextField
The variable is being saved to textField.text instead of textField
The alert is printing self.enteredName instead of self.enteredName.text
So my question is this, why did the bottom text block not work properly while the top block did?
The addTextFieldConfigurationHandler method is called before the user has an opportunity to to type anything and press save (and therefore, before the code you put in the saveAction is ever executed).
The only time in the bottom example you ever assign anything to your enteredName string is when you're adding the text field configuration handler. The text field configuration handler is never called at any point after the user sees the text field (so never after the user has an opportunity to type in the text field).
And the string variable can't just magically get the value from the text field.