-
-
Save protspace/24c74761e14d851ce1f75cf352db7693 to your computer and use it in GitHub Desktop.
Small modification of @OleB's UIAlertController extension for user authorization (login, password). Source: https://oleb.net/2018/uialertcontroller-textfield/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
/// A validation rule for text input. | |
public enum TextValidationRule { | |
/// Any input is valid, including an empty string. | |
case noRestriction | |
/// The input must not be empty. | |
case nonEmpty | |
/// The enitre input must match a regular expression. A matching substring is not enough. | |
case regularExpression(NSRegularExpression) | |
/// The input is valid if the predicate function returns `true`. | |
case predicate((String) -> Bool) | |
public func isValid(_ input: String) -> Bool { | |
switch self { | |
case .noRestriction: | |
return true | |
case .nonEmpty: | |
return !input.isEmpty | |
case .regularExpression(let regex): | |
let fullNSRange = NSRange(input.startIndex..., in: input) | |
return regex.rangeOfFirstMatch(in: input, options: .anchored, range: fullNSRange) == fullNSRange | |
case .predicate(let p): | |
return p(input) | |
} | |
} | |
} | |
extension UIAlertController { | |
public enum TextInputResult { | |
/// The user tapped Cancel. | |
case cancel | |
/// The user tapped the OK button. The payload is the text they entered in the text field. | |
case ok(String) | |
} | |
/// Creates a fully configured alert controller with one text field for text input, a Cancel and | |
/// and an OK button. | |
/// | |
/// - Parameters: | |
/// - title: The title of the alert view. | |
/// - message: The message of the alert view. | |
/// - cancelButtonTitle: The title of the Cancel button. | |
/// - okButtonTitle: The title of the OK button. | |
/// - validationRule: The OK button will be disabled as long as the entered text doesn't pass | |
/// the validation. The default value is `.noRestriction` (any input is valid, including | |
/// an empty string). | |
/// - textFieldConfiguration: Use this to configure the text field (e.g. set placeholder text). | |
/// - onCompletion: Called when the user closes the alert view. The argument tells you whether | |
/// the user tapped the Close or the OK button (in which case this delivers the entered text). | |
public convenience init(title: String, message: String? = nil, | |
cancelButtonTitle: String, okButtonTitle: String, | |
loginValidation loginValidationRule: TextValidationRule = .noRestriction, | |
loginTextFieldConfiguration: ((UITextField) -> Void)? = nil, | |
passwordValidation passwordValidationRule: TextValidationRule = .noRestriction, | |
passwordTextFieldConfiguration: ((UITextField) -> Void)? = nil, | |
onCompletion: @escaping (TextInputResult) -> Void) { | |
self.init(title: title, message: message, preferredStyle: .alert) | |
/// Observes a UITextField for various events and reports them via callbacks. | |
/// Sets itself as the text field's delegate and target-action target. | |
class TextFieldObserver: NSObject, UITextFieldDelegate { | |
let textFieldValueChanged: (UITextField) -> Void | |
let textFieldShouldReturn: (UITextField) -> Bool | |
init(textField: UITextField, valueChanged: @escaping (UITextField) -> Void, shouldReturn: @escaping (UITextField) -> Bool) { | |
self.textFieldValueChanged = valueChanged | |
self.textFieldShouldReturn = shouldReturn | |
super.init() | |
textField.delegate = self | |
textField.addTarget(self, action: #selector(TextFieldObserver.textFieldValueChanged(sender:)), for: .editingChanged) | |
} | |
@objc func textFieldValueChanged(sender: UITextField) { | |
textFieldValueChanged(sender) | |
} | |
// MARK: UITextFieldDelegate | |
func textFieldShouldReturn(_ textField: UITextField) -> Bool { | |
return textFieldShouldReturn(textField) | |
} | |
} | |
var textFieldObserver: TextFieldObserver? | |
// Every `UIAlertAction` handler must eventually call this | |
func finish(result: TextInputResult) { | |
// Capture the observer to keep it alive while the alert is on screen | |
textFieldObserver = nil | |
onCompletion(result) | |
} | |
let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel, handler: { _ in | |
finish(result: .cancel) | |
}) | |
let okAction = UIAlertAction(title: okButtonTitle, style: .default, handler: { [unowned self] _ in | |
finish(result: .ok(self.textFields?.first?.text ?? "")) | |
}) | |
addAction(cancelAction) | |
addAction(okAction) | |
preferredAction = okAction | |
addTextField(configurationHandler: { textField in | |
loginTextFieldConfiguration?(textField) | |
textFieldObserver = TextFieldObserver(textField: textField, | |
valueChanged: { textField in | |
okAction.isEnabled = loginValidationRule.isValid(textField.text ?? "") | |
}, | |
shouldReturn: { textField in | |
loginValidationRule.isValid(textField.text ?? "") | |
}) | |
}) | |
addTextField(configurationHandler: { textField in | |
passwordTextFieldConfiguration?(textField) | |
textFieldObserver = TextFieldObserver(textField: textField, | |
valueChanged: { textField in | |
okAction.isEnabled = passwordValidationRule.isValid(textField.text ?? "") | |
}, | |
shouldReturn: { textField in | |
passwordValidationRule.isValid(textField.text ?? "") | |
}) | |
}) | |
// Start with a disabled OK button if necessary | |
okAction.isEnabled = loginValidationRule.isValid(textFields?.first?.text ?? "") && passwordValidationRule.isValid(textFields?.last?.text ?? "") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment