Function textField catches mistake but still displays bad character - swift

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.

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.

Swift UITextfield and CharacterSet cannot delete previous user's input

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
}

cipher, not sure how to implement alphanumeric cipher with given protocols in place

I am trying to make the text box only take alpha numeric input only, but how do I do it in a function already without having to create another function or protocol to do so? I am only 4 weeks into learning swift, I am not sure how to use CharacterSet or Range, but I need to only take A-Z and 0-9, BUT with that input I have to be able to shift through it in that order, so if I shift it by 2 and i am on z, it will give me 1, or If i am on a and i shift by -1 it will go to 9. I have to use the protocol that I already have in place which has 2 functions, encrypt and decrypt and takes in 2 parameters, a string for the input and another string for the int to shift it by but that too takes a string, I convert that to an int to use. I am just lost on how to restrict input in the functions and shift through the indices with the int given.
protocol CipherProtocol {
func encrypt (plaintext: String, secret: String) -> String
func decrypt (output: String, secret: String) -> String
}
struct AlphanumericCesarCipher: CipherProtocol {
func encrypt(plaintext: String, secret: String) ->
String {
guard let secret = UInt32(secret) else {
return "error"
}
var encoded = ""
for character in plaintext {
if let alpha = ["A","B","C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
"0","1","2","3","4","5","6","7","8","9"] {
}
guard let firstUnicodeScalar = character.alpha.first else {
return "error"
}
let unicode = firstUnicodeScalar.value
let shiftedUnicode = unicode + secret
let shiftedCharacter = String(UnicodeScalar(UInt8(shiftedUnicode))) //what does this line mean
encoded += shiftedCharacter
}
return encoded
}
func decrypt(output: String, secret: String) -> String {
return ""
}
}
I think what you need is using of 'Regular Expressions'. In this case your text input will only accept alphanumeric or any set of characters that you specify.
Try this code in your view controller to only allow a-z,A-Z,0-9 characters as input. Then you can use your logic to shift or other tasks you need.
override func viewDidLoad() {
super.viewDidLoad()
txt_input.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
do {
let text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let regex = try NSRegularExpression(pattern: "^([a-zA-Z0-9])$", options: [])
if regex.firstMatch(in: text, options: [], range: NSMakeRange(0, text.count)) != nil {
return true
}
}
catch {
print("ERROR")
}
return false
}

Cannot convert value of type 'Int' to expected argument type 'UInt'

I am trying to call insertspaceInExpiryDateTextField() function into textField() function and getting the above error.
// Making the Expiry Date Text Field take "/" after 2 digits.
func insertspaceInExpiryDateTextField(string: String,inout andPreserveCursorPosition cursorPosition: UInt) -> String {
var stringWithAddedSpaces: String = ""
for index in 0.stride(to: string.characters.count, by: 1) {
if index != 0 && index % 2 == 0 {
stringWithAddedSpaces += " "
if index < Int(cursorPosition) {
cursorPosition += 1
}
}
let characterToAdd: Character = Array(string.characters) [index]
stringWithAddedSpaces.append(characterToAdd)
}
return stringWithAddedSpaces
}
// Calling the above function into this function
// making the Expirey Date Field take only 4 digits.... Also giving "/" after 2 digits to divide month and year.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == self.txt_expDate {
self.insertspaceInExpiryDateTextField(textField.text!, andPreserveCursorPosition: (&UInt(range.length))) // Here I get the error
txt_expDate.text! = txt_expDate.text!.stringByReplacingOccurrencesOfString(" ", withString: "/", options: NSStringCompareOptions.LiteralSearch, range: nil)
return ((txt_expDate.text?.characters.count)! >= 4 && range.length == 0) ? false : true
}
}
// Everything else works fine, except the UInt error.
// I also checked with Error 'cannot convert value of type 'int' to expected argument type 'UInt' but no use..
Is there any particular line that I am going wrong ? Thanks
Thanks
The length property of NSRange is bridged as Int and not UInt in Swift (I have no idea why; its NSUInteger in Objective C). You can check in a playground as follows:
print(type(of: NSRange(location: 0, length: 0).length))

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!