Not able to pass textfield value - ios

I'm trying to pass the value in my textfield to another view using delegates. This is how I tried to achieve that..
In the 2nd view from where the value is to be passed to main view, this is what I have written..
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let _ = raDelegate {
raDelegate?.durationChanged(hrs: string)
}
return true
}
And in the first view, this is what I have...
func durationChanged(hrs: String) {
myView.durationTextField.text = hrs
}
Now the issue is that if I type in say "5" in the textfield, in the durationChanged function, on applying breakpoint, I get the value in hrs as "5" itself. But when I remove the breakpoint and run, then what gets finally printed to myView.durationTextField.text is 55. Also if I print "7" after that, the "55" gets replaced by "77". While ideally what I should have got was "57".

you use it wrongly.
string on shouldChangeCharactersIn function isn't the value of textField.
for example:
1. textField.text = ""
2. input 5 -> range is (0,0), string is 5, textField.text = "" (The textField.text isn't changed yet, it depends on return value of delegate function, if true it'll replace the textField.text with in range with string)
3. input 7 -> range is (1,0), string is 7, textField.text = "5"
4. input backspace -> range is (2, 1), string is "", textField = "57"
After 4, your textField.text = "5"
so you can revise your code:
let stringAfterChanged = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
raDelegate?.durationChanged(hrs: stringAfterChanged)

You used shouldChangeCharactersIn wrongly. update your code with your controller extension
extension YourController: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let text = textField.text,
let textRange = Range(range, in: text) {
let updatedText = text.replacingCharacters(in: textRange,
with: string)
raDelegate?.durationChanged(hrs: updatedText)
}
return true
}
}
Or try this workaround
extension YourController: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
let previousText:NSString = textField.text! as NSString
let updatedText = previousText.replacingCharacters(in: range, with: string)
raDelegate?.durationChanged(hrs: updatedText)
return true
}

Related

textField shouldChangeCharactersInRange function Swift5

My goal here is to format the text as the user is typing
I am not completely sure how to use the
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool { }
I have tried several posibilities with this and the closer that I could get to what I want was the answer for this thread:
extension MyViewController: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let text = textField.text,
let textRange = Range(range, in: text) {
let updatedText = text.replacingCharacters(in: textRange,
with: string)
myvalidator(text: updatedText)
}
return true
}
}
I got really close to what I need. I can get the console to print the entered text and the new text for example
Input: 498746454
Console:
$ 0
4$ 4
49$ 49
498$ 498
4987$ 4 987
49874$ 49 874
498746$ 498 746
4987464$ 4 987 464
49874645$ 49 874 645
that's where my first problem is, because output is 1 character behind than the input in the textfield
How can I get it to change exactly when the text is inputed??
And my second question is how do I get it to display on the textfield??
This is the code I am using:
func myvalidator(text: String){
print(text)
}
func textField(_ textField: UITextField,shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = amount.text,
let textRange = Range(range, in: text) {
let updatedText = amount.text!.replacingCharacters(in: textRange, with: "$ \((amount.text! as NSString).doubleValue.formattedWithSeparator)")
myvalidator(text: updatedText)
}
return true
}
I did my research here, here, and here and several threads other but the answer either give me similar problems or are outdated for Swift 5
To get the current input text, after a character changes, you have to add a custom function to your textField.
yourTextField.addTarget(self, action: #selector(changedCharacters(textField:)), for: .editingChanged)
your function can look like this:
#objc func changedCharacters(textField: UITextField){
guard let input = textField.text else { return }
// do with your text whatever you want
}
As per your question, whenever the user types some number you should know the number and updated number should be print in the console. Try below code
func textField(_ textField: UITextField,shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let updatedText = newString + "$ \(String(describing: Double(newString) ?? 0.0))"
myvalidator(text: updatedText)
return true
}
replace your delegate with above one, you should know that whenever new character you type this delegate helps you to display that character in your textField or not. Last Parameter of this delegate string contains a character user typed recently.

iOS Swift decimal input in UITextfield from right to left

None of the solutions mentioned here:
How to input currency format on a text field (from right to left) using Swift?
work for me. Please don't mark this as duplicate.
I cannot subclass UITextField to give my own implementation.(This UITextField is also used elsewhere).
The only way that I see is to format replacementText string (user input string) in this method:
override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
var replacementText = (currentText as NSString).replacingCharacters(in: range, with: string)
}
So when user enters a number it should show like this: 0.00.
When the user taps 1. We should have 0.01
When the user taps 2. We should display 0.12
When the user taps 3. We should display 1.23
When the user taps 4. We should display 12.34
private var enteredText: String = ""
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if enteredText.count > 0 && string == "" && range.length == 1 {
//Handles backspace
enteredText.removeLast()
}
else {
enteredText += string
}
return true
}
func textFieldDidChangeCharacter(textField:UITextField) {
if let number = NumberFormatter().number(from: enteredText) {
let double = number.doubleValue/100.0
let string = String(format: "%0.2f", double)
textField.text = string
}
}

iOS 13 Crash with SwipeKeyboard and textfield:shouldChangeCharactersIn:

In iOS 13, when implementing shouldChangeCharactersIn via the UITextfieldDelegate, the application crashes when using the swiping keyboard.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text as NSString? {
let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
textField.text = txtAfterUpdate
}
return false
}
Is this an Apple bug?
I was able to reproduce this - if you mutate the state of the text on a UITextField during swipe entry - and only during swipe entry, it'll attempt to reinsert the swiped content (even if you return false), which retriggers your delegate event, which kicks off the recursive cycle.
It's a bit of a hack but you could catch it with something like
private var lastEntry: String?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.count > 1 && string == lastEntry { // implies we're swiping or pasting
print("Caught unwanted recursion")
return
}
lastEntry = string
if let text = textField.text as NSString? {
let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
textField.text = txtAfterUpdate
}
return false
}
It'll stop users from pasting/swiping the same thing twice in a row, but at least it'll let them swipe while Apple fixes their problem.
I used UIPasteboard to identify when the user is pasting and then leave the text as the user entered using the swipe like this:
public func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
//check if the user used swipe keyboard
if string.count > 1 && string != UIPasteboard.general.string ?? "" {
return true
}
//do the text treatment
return false
}
I also realized that the TextField only accepts static strings when using swipe keyboard.
Hope it Helps.
Before setting text you can reset delegate and after set it to self again.
But this solution has one problem if textfield is empty - text will be doubled.
Му code example:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText: String = textField.text ?? ""
if #available(iOS 13, *) {
textField.delegate = nil
let resultText = editedText
textField.text = resultText
if currentText.isEmpty, textField.text != resultText {
textField.text = resultText
}
textField.delegate = self
} else {
textField.text = input.result
}
return false
}

Disallowing empty input

How do we disallow empty input on textField?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let oldText = textField.text!
let stringRange = Range(range, in:oldText)!
let newText = oldText.replacingCharacters(in: stringRange, with: string)
doneBarButton.isEnabled = !newText.isEmpty
return true
}
Is there other ways? or using other textField delegate methods to do this?
Yes, this is basically how you do it.
Alternatively, if all you want to know if the field is empty or not, just figure out the length of the result:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let length = (textField.text?.count ?? 0) + string.count - range.length
doneBarButton.isEnabled = length > 0
return true
}
You may also want to set enablesReturnKeyAutomatically for the text field to true, so that the keyboard doesn’t enable the “return” key unless there is text in the field.

How shouldChangeCharactersInRange works in Swift?

I'm using shouldChangeCharactersInRange as a way of using on-the-fly type search.
However I'm having a problem, shouldChangeCharactersInRange gets called before the text field actually updates:
In Objective C, I solved this using using below:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * searchStr = [textField.text stringByReplacingCharactersInRange:range withString:string];
return YES;
}
However, I've tried writing this in Swift:
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
let txtAfterUpdate:NSString = self.projectSearchTxtFld.text as NSString
txtAfterUpdate.stringByReplacingCharactersInRange(range, withString: string)
self.callMyMethod(txtAfterUpdate)
return true
}
The method still gets called before I get a value?
Swift 4, Swift 5
This method doesn't use NSString
// MARK: - UITextFieldDelegate
extension MyViewController: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let text = textField.text,
let textRange = Range(range, in: text) {
let updatedText = text.replacingCharacters(in: textRange,
with: string)
myvalidator(text: updatedText)
}
return true
}
}
Note. Be careful when you use a secured text field.
stringByReplacingCharactersInRange return a new string, so how about:
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
if let text = textField.text as NSString? {
let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
self.callMyMethod(txtAfterUpdate)
}
return true
}
Swift 3 & 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let textFieldText: NSString = (textField.text ?? "") as NSString
let txtAfterUpdate = textFieldText.replacingCharacters(in: range, with: string)
callMyMethod(txtAfterUpdate)
return true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
callMyMethod("")
return true
}
Swift 2.2
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let textFieldText: NSString = textField.text ?? ""
let txtAfterUpdate = textFieldText.stringByReplacingCharactersInRange(range, withString: string)
callMyMethod(txtAfterUpdate)
return true
}
func textFieldShouldClear(textField: UITextField) -> Bool {
callMyMethod("")
return true
}
Though the textField.text property is an optional, it cannot be set to nil. Setting it to nil is changed to empty string within UITextField. In the code above, that is why textFieldText is set to empty string if textField.text is nil (via the nil coalescing operator ??).
Implementing textFieldShouldClear(_:) handles the case where the text field's clear button is visible and tapped.
In Swift 3 it would look like this:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text: NSString = (textField.text ?? "") as NSString
let resultString = text.replacingCharacters(in: range, with: string)
return true
}
shouldChangeCharactersIn is called on every key press.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// get the current text, or use an empty string if that failed
let currentText = textField.text ?? ""
// attempt to read the range they are trying to change, or exit if we can't
guard let stringRange = Range(range, in: currentText) else { return false }
// add their new text to the existing text
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
// make sure the result is under 16 characters
return updatedText.count <= 16
}
shouldChangeCharactersInRange
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool { }
This function is called when changes are made but UI is not updated and waiting for your choice
Take a look at returned bool value
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
If you return true - it means that iOS accept changes(text, caret...)
If you return false - it means that you are responsible for all this stuff
Swift 3
If you want to pre-process the characters the user typed or pasted, the following solution workes like a charm
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let strippedString = <change replacements string so it fits your requirement - strip, trim, etc>
// replace current content with stripped content
if let replaceStart = textField.position(from: textField.beginningOfDocument, offset: range.location),
let replaceEnd = textField.position(from: replaceStart, offset: range.length),
let textRange = textField.textRange(from: replaceStart, to: replaceEnd) {
textField.replace(textRange, withText: strippedString)
}
return false
}
Find it here: https://gist.github.com/Blackjacx/2198d86442ec9b9b05c0801f4e392047
This is essentially #Vyacheslav's answer independently arrived at for my own use case, just in case the stylistic approach resonates :-)
func textField(_ textField: UITextField, shouldChangeCharactersIn nsRange: NSRange, replacementString: String) -> Bool {
let range = Range(nsRange, in: textField.text!)!
let textWouldBecome = textField.text!.replacingCharacters(in: range, with: replacementString)
if textWouldBecome != eventModel.title {
self.navigationItem.setHidesBackButton(true, animated: true)
} else {
self.navigationItem.setHidesBackButton(false, animated: true)
}
return true
}
Replace eventModel.title with whatever you're checking for the change against obviously.
To get the exact text in the my UITextField component in Swift 3.0 I used:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let enteredTxt = textField.text! + string
doSomethingWithTxt(enteredTxt) //some custom method
}

Resources