Swift 5, Xcode 14.2
I've got two UIViewController
s that both have to display the same UIAlertController
(only one is loaded at a time). Instead of using the same code twice, I added it to my existing static class that's already used to pass data around (only accessed by one thread at a time).
In my static class:
static func createLoginDialog(buttonPressed: @escaping (_ username:String, _ pw:String) -> Void) -> UIAlertController {
let alert = UIAlertController(title: "Login", message: "", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Log in", style: .default, handler: { (action: UIAlertAction!) in
let u = alert.textFields![0].text!
let p = alert.textFields![1].text!
buttonPressed(u,p)
}))
alert.addTextField { textField in
textField.placeholder = "Enter Username"
}
alert.addTextField { textField in
textField.placeholder = "Enter Password"
}
return alert
}
Calling code in the UIViewController
classes:
func doSomethingAndShowAlert() {
//Do something
let alert = MyStaticClass.createLoginDialog() {
(username,pw) in
//Do stuff with non-empty input
}
self.present(alert,animated: true, completion: nil)
}
With this code the dialog is dismissed once you press the button, no matter what the input is. The input is checked for validity in the calling function but I don't want to close the dialog, unless there's text in both UITextField
s. According to answers to other questions, you can't keep the dialog from being dismissed automatically but you can disable the action/button until there's text, which is fine with me.
There are a couple of suggestions here (only for a single UITextField
) but Xcode complains about the @objc
/action: #selector
if I add it to my code (because my class is static?). I also tried to combine this single TF version with this multi TF version but the action function is never called (maybe because I don't set it in viewDidLoad()
?).
I'm aware that this would be a lot easier with a custom view that's displayed instead of a UIAlertController
but I want to try it this way.
How do I check if there's text in both UITextField
s, before enabling the button, in my static class/function?
Quickly written to have the logic using your static method:
static func createLoginDialog(buttonPressed: @escaping (_ username:String, _ pw:String) -> Void) -> UIAlertController {
let alert = UIAlertController(title: "Login", message: "", preferredStyle: .alert)
let loginAction = UIAlertAction(title: "Log in", style: .default, handler: { _ in
let u = alert.textFields![0].text!
let p = alert.textFields![1].text!
buttonPressed(u,p)
})
alert.addAction(loginAction)
loginAction.isEnabled = false
var textFields: [UITextField] = []
alert.addTextField { textField in
textField.placeholder = "Enter Username"
textFields.append(textField)
NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification,
object: textField,
queue: .main) { _ in
loginAction.isEnabled = textFieldsAreValid(textFields)
}
}
alert.addTextField { textField in
textField.placeholder = "Enter Password"
textFields.append(textField)
NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification,
object: textField,
queue: .main) { _ in
loginAction.isEnabled = textFieldsAreValid(textFields)
}
}
func textFieldsAreValid(_ textFields: [UITextField]) -> Bool {
textFields.allSatisfy { !($0.text ?? "").isEmpty } //Do the needed tests, I just check if not empty
}
return alert
}
But as Vadian said, it maybe be better to have an extension on UIViewController
.
We can imagine that you have
protocol LoginProtocol: Protocol {
}
extension LoginProtocol where Self: UIViewController {
func loginAlert(buttonPressed: @escaping (_ username:String, _ pw:String) -> Void) -> UIAlertController {
//The previous code
}
func showLoginAlert(buttonPressed: @escaping (_ username:String, _ pw:String) -> Void) {
let alert = loginAlert(buttonPressed: buttonPressed)
present(alert, animated: true)
}
}
I know that sometimes, you want to call it from classes that aren't UIViewController
, but in theory, that object (be it a subview etc.), should tell its parent ViewController to show the login alert, it shouldn't do it itself. That's why I'd prefer using that way.
And on each ViewController that can show the alert:
extension MyCustomVC: LoginProtocol {}
And it can call showLoginAlert(...)
.