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)
}
}