Reputation: 2598
I'm trying to make some features on UITextField
which for input amount
Wrapper view contains this UITextField
, and that view conforms UITextFieldDelegate
so i implement textField(_:shouldChangeCharactersIn:replacementString:)
method in Wrapper view class
In that method, I manipulate the input text from keyboard(add comma or block input when amount exceed maximum input amount)
It does work well in system keyboard, but not in custom keyboard
In custom keyboard, i make buttons and if some button pressed, i set the text to textfield what i make (amount text field) like this
textField.text = someText
but set text to textfield explicitly, it doesn't trigger textField(_:shouldChangeCharactersIn:replacementString:)
method
How can i trigger textField(_:shouldChangeCharactersIn:replacementString:)
method when using custom keyboard
Upvotes: 0
Views: 1293
Reputation: 77433
You cannot "trigger" shouldChangeCharactersIn
, but there are various ways to approach this.
One method would be to move your "validation" code into its own function. You could then call that function both from shouldChangeCharactersIn
and from your custom keyboard input.
So, for a very simple example - allowing only Numeric entry - it might look something like this:
func myShouldChangeCharacters(_ textField: UITextField, in range: NSRange, replacementString string: String) -> Bool {
// whatever you want to do to validate the input
if !"0123456789".contains(string) {
return false
}
return true
}
// textField delegate call
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return myShouldChangeCharacters(textField, in: range, replacementString: string)
}
// button tapped
@objc func btnTapped(_ sender: Any) {
// make sure we're
// called by a button
// get the button title
// convert textField's .selectedTextRange to a NSRange
guard let btn = sender as? UIButton,
let str = btn.currentTitle,
let rng = myTextField.selectedRange
else {
return
}
if myShouldChangeCharacters(myTextField, in: rng, replacementString: str) {
myTextField.insertText(str)
}
}
uses this extension:
// helper extension to convert
// text field's selectedTextRange
// to a NSRange
extension UITextInput {
var selectedRange: NSRange? {
guard let range = selectedTextRange else { return nil }
let location = offset(from: beginningOfDocument, to: range.start)
let length = offset(from: range.start, to: range.end)
return NSRange(location: location, length: length)
}
}
Here's a complete example that has a text field at the top, with 4 buttons below it labeled 1, 2, A, B, and only allows Numerics:
class ViewController: UIViewController, UITextFieldDelegate {
let myTextField: UITextField = {
let v = UITextField()
v.borderStyle = .roundedRect
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
let btnsStack: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.distribution = .fillEqually
v.spacing = 40.0
return v
}()
// create 4 buttons
["1", "2", "A", "B"].forEach { str in
let b = UIButton()
b.setTitle(str, for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
b.backgroundColor = .systemRed
b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
btnsStack.addArrangedSubview(b)
}
[myTextField, btnsStack].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
myTextField.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
myTextField.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
myTextField.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
btnsStack.topAnchor.constraint(equalTo: myTextField.bottomAnchor, constant: 20.0),
btnsStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
btnsStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
])
myTextField.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return myShouldChangeCharacters(textField, in: range, replacementString: string)
}
@objc func btnTapped(_ sender: Any) {
// make sure we're
// called by a button
// get the button title
// convert textField's .selectedTextRange to a NSRange
guard let btn = sender as? UIButton,
let str = btn.currentTitle,
let rng = myTextField.selectedRange
else {
return
}
if myShouldChangeCharacters(myTextField, in: rng, replacementString: str) {
myTextField.insertText(str)
}
}
func myShouldChangeCharacters(_ textField: UITextField, in range: NSRange, replacementString string: String) -> Bool {
if !"0123456789".contains(string) {
return false
}
return true
}
}
// helper extension to convert
// text field's selectedTextRange
// to a NSRange
extension UITextInput {
var selectedRange: NSRange? {
guard let range = selectedTextRange else { return nil }
let location = offset(from: beginningOfDocument, to: range.start)
let length = offset(from: range.start, to: range.end)
return NSRange(location: location, length: length)
}
}
Upvotes: 1