How to use shouldChangeCharactersInRange to continuously format a number - swift

I have a UITextField validate that converts a user's numeric input. For example, if the user types in a 1, it becomes 0.01. If the user types 12, it becomes 0.12.
My code is working correctly when I have the validation code in textFieldDidEndEditing. However, I've been asked to rewrite the code a bit so that the validation occurs as the user is typing as opposed to when they're finished with the textfield, so it makes sense to use shouldChangeCharactersInRange. However, I'm a little confused how to write my code now.
Old code (working):
func textFieldDidEndEditing(_ textField: UITextField) {
if var text = textField.text,
let float = Float(text) {
let formattedFloat = float/100
if let formatterExists = formatter {
text = String.localizedStringWithFormat("\(String(describing: formatterExists))", formattedFloat)
}
let numericString = text.replacingOccurrences(of: ",", with: "")
cellModel.stringInput = numericString
}
}
New code (not working):
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let float = Float(string) {
let formattedFloat = float/100
var text = string
if let formatterExists = formatter {
text = String.localizedStringWithFormat("\(String(describing: formatterExists))", formattedFloat)
}
let numericString = text.replacingOccurrences(of: ",", with: "")
print(numericString)
textField.text = text
cellModel.stringInput = numericString
}
return false
}
I know that in my shouldChangeCharactersInRange code, everytime I type a character, it is taking that character and applying the validation, and only doing it for a single character (as in if I type 1, I get a 0.01, and if I type a 2, the previous character gets replaced and I get a 0.02).
I'm wondering what is a good method for a user to be able to continuously input a number and keep having the validation applied as the user types.
Thanks!

Related

TextField i.e. showing % during text input

how to append % sign after input
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text! as NSString
let newText = currentText.replacingCharacters(in: range, with: string)
if newText.hasSuffix("%"){
textField.text = newText
}else{
textField.text = "\(newText) %"
}
return true
}
}
as in the picture i just want 1 % sign after the numbers.
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619599-textfield says:
Return Value: true if the specified text range should be replaced;
otherwise, false to keep the old text.
probably because you return true swift updates with the old text replacing the text property of the text-field you just set. Try returning false as this 'keeps the old text' which is already updated by your code.
The reason is func shouldChangeCharactersIn call before the text is input so it is wrong. What we want in here is after the value changed, add % at the end if not have. Here is the code for do that.
// add action for value change recognized
textField.addTarget(self, action: #selector(didChangeValueTextField(textField:)), for: .editingChanged)
#objc final private func didChangeValueTextField(textField: UITextField)
{
let text = textField.text ?? ""
if !text.hasSuffix("%") {
textField.text! = text + "%"
// make cursor position to be left 1 index because final index is %
let newPosition = textField.position(from: textField.endOfDocument, offset: -1)!
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
}

Get word that is being typed

I'm implementing a tagging feature similar to that of Facebook. So when I type # and some character(s) after it, the function should return the word being typed.
So if the textView contains (and the cursor is at c)
Hello #Jac !
The function should return "#Jac"
If it contains (and the cursor is at a)
Hello #Ja !
Then the function should return "#Ja"
The final string of both examples would be,
Hello Jack !
I have attempted multiple solutions but none are working. One particular question was very similar to my question, but the solution has errors. Here is the link.
Update 1
Here is how I've set the delegate on the textView,
postView.textView.delegate = self
This is the code for detecting if the # character was tapped (display the friends list table, if it was)
if let text = self?.characterBeforeCursor() {
if (text == "#" && self?.friends.count != 0) {
self?.friendTableView.isHidden = false
} else {
var word // Need to get the word being typed
self?.displayedFriends = (self?.displayedFriends.filter { ($0["firstName"]?.hasPrefix(word))! })!
}
}
Update 2
The solution below did not solve the problem. It is returning all text in the textfield instead of just the word that is being typed.
There is a delegate function called shouldChangeCharactersInRange. From there you can get the current text after the user tapped the letter.
Make sure you use UITextFieldDelegate in your class declaration and set the textField's delegate to self.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//get the updated text from the text field like this:
let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
//note that we *need* to use the text as NSString, because the delegate method gives us an NSRange, rather than a Range which we can't use on String, but NSString, so we need to convert that first
return true //so the text is visually updated in the textfield
}
Edit
I just saw that you posted about UITextView. It's pretty much the same:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let text = (textView.text as NSString?)?.replacingCharacters(in: range, with: text)
return true
}
Edit 2
You need to also assign the text view's text to your variable in the code snippet you provided:
} else {
var word = postView.textView.text
self?.displayedFriends = (self?.displayedFriends.filter { ($0["firstName"]?.hasPrefix(word))! })!
}

How to prevent username textfield for not using whitespace or special characters swift 3.0 [duplicate]

I am creating a trivia application that asks for a username on start up. I'd like to make it impossible to use characters such as #$#!^& etc (also including "space"). I took a look at this post here but it is written entirely in Objective-C. Thanks in advance.
Swift 4 iOS 11.2.x based on using an extension, tests to see if a string is a valid hex number in this example.
extension String {
var containsValidCharacter: Bool {
guard self != "" else { return true }
let hexSet = CharacterSet(charactersIn: "1234567890ABCDEFabcdef")
let newSet = CharacterSet(charactersIn: self)
return hexSet.isSuperset(of: newSet)
}
}
You use it like with the UITextFieldDelegate.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return (string.containsValidCharacter)
}
Although I read in an earlier post that CharacterSets do not support characters that are composed of more than one Unicode.Scalar; so use with caution I guess.
Since you're explicitly asking for Swift, I've translated the top asnwer in the linked question.
let notAllowedCharacters = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.";
func textField(
textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool
{
let set = NSCharacterSet(charactersInString: notAllowedCharacters);
let inverted = set.invertedSet;
let filtered = string
.componentsSeparatedByCharactersInSet(inverted)
.joinWithSeparator("");
return filtered != string;
}
internal func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if let text = string{
if text == "#" || text == "$" || text == "!"{ \\and so on
return false
}
}
return true
}
Swift 2.3
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let characters = ["#", "$", "!", "&","#"]
for character in characters{
if string == character{
print("This characters are not allowed")
return false
}
}
}
So this is probably the most robust way to restrict Spaces. Using this user won't be able to Paste/Type Whitespaces
This is how you can Implement using Swift 3.
Add below mentioned extension snippet to a Swift file;
extension String {
var containsWhitespace: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x20:
return true
default:
continue
}
}
return false
}
}
In your ViewController Swift file drag out your Editing Changed Instance and a Referencing Outlet of UITextField from Storyboard, the one mentioned in picture below:
Use the dragged Instances as mentioned below:
Referencing Outlet as:
#IBOutlet weak var textField: UITextField!
and Editing Changed as:
#IBAction func textChanged(_ sender: UITextField) {
if (textField.text!.containsWhitespace) == true {
print("Restrict/Delete Whitespace")
emailField.deleteBackward()
} else {
print("If Its not Whitespace, Its allowed.")
}
}
This will detect and remove whitespace as soon as user tries to type/paste it.
Swift 4 iOS 11.2.x based on using an extension, tests to see if a string is a valid hex number in this example.
extension String {
var containsValidCharacter: Bool {
guard self != "" else { return true }
let hexSet = CharacterSet(charactersIn: "1234567890ABCDEFabcdef")
let newSet = CharacterSet(charactersIn: self)
return hexSet.isSuperset(of: newSet)
}
}
You use it like with the UITextFieldDelegate.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return (string.containsValidCharacter)
}
Swift : 3 and a different approach:
Add a target function for the text field change in your viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
}
in the target function, simply detect the entered char and replace it with blank. I have tested it and it prevents the user from entering any non desirable characters in the text field.
func textFieldDidChange(textField: UITextField) {
if let textInField = textField.text{
if let lastChar = textInField.characters.last{
//here include more characters which you don't want user to put in the text field
if(lastChar == "*")
{
textField.text = textInField.substring(to: textInField.index(before: textInField.endIndex))
}
}
}
}
Adding on to what #Evdzhan Mustafa said. You want to add a return statement in case the string is empty. Without it you won't be able to delete your text. Modified Code Below:
Swift 3 Version
let notAllowedCharacters = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.";
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.isEmpty{
return true
}
print("String: \(string)")
let set = NSCharacterSet(charactersIn: notAllowedCharacters);
let inverted = set.inverted;
let filtered = string.components(separatedBy: inverted).joined(separator: "")
print("String Filtered: \(filtered)")
return filtered != string;
}

Swift: Add comma and $ in the textfield when typing

I want the users see the $ and comma when they are typing in a textfield from numberPad (Without a decimal dot). Previously I got help and use below codes. But clients will not type the decimal (Only Int value).
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let oldText = textField.text! as NSString
var newText = oldText.stringByReplacingCharactersInRange(range, withString: string) as NSString!
var newTextString = String(newText)
let digits = NSCharacterSet.decimalDigitCharacterSet()
var digitText = ""
for c in newTextString.unicodeScalars {
if digits.longCharacterIsMember(c.value) {
digitText.append(c)
}
}
let formatter = NSNumberFormatter()
// formatter.usesSignificantDigits = false
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
formatter.locale = NSLocale(localeIdentifier: "en_US")
let numberFromField = (NSString(string: digitText).doubleValue) / 100
newText = formatter.stringFromNumber(numberFromField)
textField.text = String(newText)
return false
}
When typing, it always starts from the second decimal unit, How to remove the ".00" via editing the code and let it start from the unit? I tried for a long time, thanks in advance.
First, you got some bad advice. You should not be using shouldChangeCharactersInRange to change the characters in a text field. That's for checking if the characters typed are valid for the field. The only thing you should do in this method is return true if the user entered digits or delete, otherwise false. (Remember, the user may be using an external keyboard so just having the keypad up isn't good enough to stop non-digit entry.)
Instead you should be using an #IBAction connected to the field's EditingChanged event. Inside this method is where you should update the text.
#IBAction func editingChanged(sender: UITextField) {
let digits = sender.text?.digitsOnly ?? "0"
sender.text = "$\(digits).00" // If I understand what you want.
}
The below extension should be somewhere in your code base. It's generally useful so store it in a gist or something, you will likely need it in future projects.
extension String {
var digitsOnly: String {
return componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
}
}
You have to make sure that the IBAction is attached to the EditingChanged event:
For Swift 4x
extension String {
var digitsOnly: String {
return components(separatedBy: NSCharacterSet.decimalDigits.inverted).joined(separator: "")
}
}

Force patterns in textfields

(Swift)
So the problem is:
My app (which is a kind of calculator) is crashing when the user puts in the textfield things that can't be calculated.
For example, if he types " -4-.", the app won't be able to do the math.
So, a pattern must be followed.
The following characters are allowed: 1234567890.-
The minus sign can only be typed when it is the first character in the textfield and cannot be typed again.
The point can only be typed after a number, and cannot be typed again.
Well you would have to determine:
When the user clicks on a number/digit/character, you would have to do a:
//Goes at top of one of your classes
var decimalCount:Int = 0
//At location of tap for character
if(decimalCount < 1) {
textField.text += "."
decimalCount += 1
}
This ideology could be applied to "-" as well.
Some how i have understood your question. According to my assumption our task is to validate the input for proper math function.Ok here we go.
First of all declare a bool variable at top of your class
var isNonNumericCharactersAllowes = true
At first we need to make our textfield to respond according to user input.So add delegate to text field and add the following delegate method.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//1. To make sure that this is applicable to only particular textfield add tag.
if textField.tag == 1 {
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
//Helps to react only while typing and not while clearing text
if (isBackSpace != -92) {
let numbersOnly = NSCharacterSet(charactersInString: "1234567890")
let characterSetFromTextField = NSCharacterSet(charactersInString: string)
let Validate:Bool = numbersOnly .isSupersetOfSet(characterSetFromTextField)
if !Validate {
if isNonNumericCharactersAllowes {
isNonNumericCharactersAllowes = false
return true
}
return false
}
isNonNumericCharactersAllowes = true
}
}
return true
}
The above method stops unusual text entry's such as 0..012,--4,4++ etc..
Now while hitting calculate button we need to some validation.Add the following code in IBAction.
#IBAction func calculate(sender: AnyObject) {
let textContent:String!
textContent = textFieldTwo.text
var characterContainer = textContent.characters.map { String($0) }
let numbersOnly = NSCharacterSet(charactersInString: "1234567890")
let lastObjectOfString = NSCharacterSet(charactersInString: characterContainer.last!)
let Validate:Bool = numbersOnly .isSupersetOfSet(lastObjectOfString)
if !Validate {
characterContainer .removeLast()
textFieldTwo.text = characterContainer .joinWithSeparator("")
}
}
This above validation helps in removing things like 30+20+,4+4+, etc.. i.e removes unused operators at the end.