Swift UITextfield and CharacterSet cannot delete previous user's input - swift

I'm writing code restricting the user's ability to enter alphabetic texts and repeating decimal into the textField.
In the code below my text field were able to accept only user's numeric input and so does the code I commented out however, the only difference is the user can't delete after he/she type in a number.
Whereas, the user were able to delete after he/she type in the number if I replace the current code with the commented code.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimalSeperator = textField.text?.range(of: ".")
let replacementTextHasDecimalSeperator = string.range(of: ".")
// let replacementTextCharacterSet = CharacterSet(charactersIn: string)
// let replacementTextIsAlphabetic = (replacementTextCharacterSet.isDisjoint(with: CharacterSet.letters) == false)
///This means an element in set A intersect with an element in set B
let replacementTextCharacterSet = CharacterSet(charactersIn: string)
let replacementTextIsAlphabetic = (replacementTextCharacterSet.isSubset(of: CharacterSet.letters) == true)
if (existingTextHasDecimalSeperator != nil && replacementTextHasDecimalSeperator != nil) || replacementTextIsAlphabetic {
return false
} else {
return true
}
The code that was commented out above work as intended however, the current code isn't and I failed to understand why.
Can you please explain to me the reason why I can't delete what I input afterward.
If I use .isSubset = true the logic seems the same to me.

you need to handle backspace in your shouldChangeCharactersIn function
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619599-textfield
string
The replacement string for the specified range. During typing,
this parameter normally contains only the single new character that
was typed, but it may contain more characters if the user is pasting
text. When the user deletes one or more characters, the replacement
string is empty.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (string.isEmpty){
print("Backspace")
}
return true
}

Related

How can I restrict user input when the value inputed is greater than a certain value Swift 5.1

My current code is:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.transferBalanceTextField {
if transferBalanceTextField.text! > currentBalance.text! {
return false
}
}
return true
}
But there are issues with this because if I type in a 6 first and the currentBalance is 582, it doesn't let me type in another digit because it believes the 6 is greater then 582, HELP!
Convert string to Ints in your case, and check it:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.transferBalanceTextField {
if let str1 = transferBalanceTextField.text,
let int1 = Int(str1),
let str2 = currentBalance.text,
let int2 = Int(str2),
int1 > int2 {
return false
}
}
return true
}
Avoid force unwrap as much as possible - it'll help you a lot when code will be big.
UPDATE after comments
Simple code above only shows converting Strings to Ints and testing it. I thought it's clear, you see it and understand.
So - if you want full test according to your case - you have also take into account what exactly this function check (should in its name) and how to deal with it.
You have two states of your field - current and next one after possible update (should you change the text in field on user input or not?). Above simple code only test current state before new user input. You have to do checking with future possible text value when new user input already accepted to the field.
For example how it work now using code above (lets user already input 100 in first field and have 500 in another field):
user press "0" - want to enter 1000
you got call of this method
you check old value - 100 - against another value and decide should it be updated or not
method return true - (should change)
and it change it - field become 1000
after it you can't update field - method now will always return false (1000 is over 500)
What you need to do:
create string what should be in field after user input (in my example it must be 1000) - you have current value + new input + range on this new input
and check against it and another value.
Why it's matter - in this method you can check all cases of user input - simple input one char by one, paste from clipboard, replacing some characters, input in middle of string, full clear and so on - and all this must be done without and before real field visual updating.
Here is full code, you should use for full your case:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.loginField {
if let str1 = loginField.text,
let str2 = passwordField.text,
let rangeExpr = Range(range, in: str1) {
let strFuture = str1.replacingCharacters(in: rangeExpr, with: string)
if let int1 = Int(strFuture),
let int2 = Int(str2),
int1 > int2 {
return false
}
}
}
return true
}
main change is in line
let strFuture = str1.replacingCharacters(in: rangeExpr, with: string)
Please, read methods you use, how it's work, and what your should do for it carefully - and you will get your progress much faster.

How to get what the user is typing and display it on a label in swift

I'm making a form in Swift, and want to get what the user is typing to display it on the screen as they type.
I've tried to use the following code to get what the user is typing as they type but it is not showing:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
[enter image description here][1]
[1]: https://i.stack.imgur.com/8xfdJ.png
UITextView will show what the user is typing, but if you need the actual text as the user is typing, the method you used is fine, you just need to perform the replacement since the method you used is asking for permission to change the current text depending on the new input, so you get the final text by:
if let text = textField.text, let range = Range(range, in: text) {
let newText = text.replacingCharacters(in: range, with: string)
print(newText) // <-- String of the new text, do what you want with it
}

City Name validation Regex Swift 4

I have been trying to implement a regex validation for a given city name.
My regex should match cities names like:
NY
San Francisco
München
København
Saint-Tropez
St. Lucia
So far I've searched the web for such-like regex but I've been having a hard time trying to implement it in swift.
this is the regex I've come up with, it seems to work in other languages but not in swift:
^[a-zA-Z\\u0080-\\u024F\\s\\/\\-\\)\(\`\.\"\']+$
Also as additional info, I'd like to implement it in the UITextField delegate method:
shouldChangeCharactersIn
something like this:
override func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
return validNameRegex.matches(string)
}
using a NSRegularExpression Extension:
extension NSRegularExpression {
convenience init(_ pattern: String) {
do {
try self.init(pattern: pattern)
} catch {
preconditionFailure("Illegal regular expression: \(pattern).")
}
}
func matches(_ string: String) -> Bool {
let range = NSRange(location: 0, length: string.utf16.count)
return firstMatch(in: string, options: [], range: range) != nil
}
}
Hope someone can help me out. Thanks in advance, and happy new year :)
In the end I ended up using this regex:
^[a-zA-Z\u{0080}-\u{024F}\\s\\/\\-\\)\\(\\`\\.\\\"\\']*$
the main problem was scaping the "\" characters and the { } in the unicode

Function textField catches mistake but still displays bad character

In the function textField, the second part (starting with 'let ...') performs perfectly by finding whenever more than one decimal point has been attempted to be entered into a textField, and does not allow more than one decimal point to be displayed in a textField.
The problem is that in the first part. Here it tries to first find if an alphabetic character has been typed. It does recognize that happening, and exits returning 'false'.
Unlike the 'repeated decimal finding part of the code, the textField displays the bad character rather than skipping it.
Maybe I don't understand how this delegated method is intended to work. I though the method somehow when it returns false it prevents that character from being added to the textField display.
func textField(_ textField:UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
for scalar in (textField.text?.unicodeScalars)! {
if NSCharacterSet.letters.contains(scalar) {
print("letter scalar found: \(scalar.value), false")
return false
}
}
let existingTextHasDecimalSeperator = textField.text?.range(of: ".") // search textField for '.', can be 'nil'
let replacementTextHasDecimalSeperator = string.range(of: ".") // next character is '.', can be 'nil'
if existingTextHasDecimalSeperator != nil, replacementTextHasDecimalSeperator != nil {
return false
} else {
return true
}
}
It's because you're searching the existing textField.text, not the new character (replacementString) for alpha characters.
Change (textField.text?.unicodeScalars)! to string.unicodeScalars and you should be set.
func textField(_ textField:UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
for scalar in string.unicodeScalars {
if NSCharacterSet.letters.contains(scalar) {
print("letter scalar found: \(scalar.value), false")
return false
}
}
let existingTextHasDecimalSeperator = textField.text?.range(of: ".") // search textField for '.', can be 'nil'
let replacementTextHasDecimalSeperator = string.range(of: ".") // next character is '.', can be 'nil'
if existingTextHasDecimalSeperator != nil, replacementTextHasDecimalSeperator != nil {
return false
} else {
return true
}
}
The suggestion of replacing the search from textField to string works! Here is a listing of the swift 3.0 version that works.

Bulleted list with NSTextList in NSTextView example?

I would want to parse user input in a NSTextView, so that a line beginning with "- " would automatically start a bulleted list. What do I have to do to the NSTextView.textStorage to enable a bulleted list so that the next bullet automatically appears when you press Enter after each bullet line?
I've found a few examples but they all insert the bullets by hand, so I was wondering what's the point of specifying let textList = NSTextList(markerFormat: "{box}", options: 0) if you then have to manually insert the box-symbol yourself?
Currently I'm trying to implement this in a custom NSTextView with an overriden shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) So a simple example that would solve the simplest input case of "- " starting an automatic bulleted list, would be highly regarded.
Update:
Here is the code I'm currently using:
override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
if string == "-" && replacementString == " " {
let textList = NSTextList(markerFormat: "{disc}", options: 0)
let textListParagraphStyle = NSMutableParagraphStyle()
textListParagraphStyle.textLists = [textList]
let attributes = [NSParagraphStyleAttributeName: textListParagraphStyle]
string = "\t\(textList.markerForItemNumber(0))\t"
textStorage?.addAttributes(attributes, range: NSMakeRange(0, textStorage!.string.length))
return false
}
else if affectedCharRange.location > 0 && replacementString == "\n\n" {
textStorage?.insertAttributedString(NSAttributedString(string: "\t"), atIndex: affectedCharRange.location)
return true
}
return true
}
I'm just trying to solve the simplest case of the user typing "- " as the first characters in the NSTextView. This is what happens with the code above:
The bigger font in the beginning comes from the typingAttributes I have set. This gets automatically overriden later as you can see.
Returning false from the else if clause and inserting the \n directly there will prevent the NSTextView from automatically adding new bullets. After the first time I return true from there, new bullets are added automatically without calling this method???
Try to add bullets using Unicode character by replace "- " with \u{2022}
textView.text = " \u{2022} This is a list item!