Formatting secure text entry by inserting visible spaces
Asked Answered
D

4

11

I want the user to enter a Social Security Number in the format ••• •• ••••. The user types the first 3 numbers, then I append a space manually. Then they enter 2 more numbers and I manually append a space. Of course, even the spaces are being displayed as •. Is there a native way to change this behavior? I am currently using a funky manual implementation of this.

Deary answered 15/7, 2016 at 17:16 Comment(2)
AFAIK, there is no native way of achieving this with secureTextEntry enabled but you can achieve the behaviour by overriding shouldChangeCharactersInRange of UITextFieldDelegate property but that would require you to disable secureTextEntryYacht
Jon, please consider accepting one of these because us future visitors can't tell which one helped you out (we don't want to waste time trying to use ones that don't work).Entozoon
S
8

What if instead of spaces, you use three different text entries? Then when each user enters the first three characters, you jump to the second text entry? When he types two more, you jump to the third text entry.

Here is an example:

EDIT: Now supports backspace (Thanks to @triple-s).

extension ViewController: UITextFieldDelegate {

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        switch textField {
            // Jump forwards
        case self.textField1 where string.characters.count == 3 :
            self.textField2.becomeFirstResponder()
        case self.textField2 where string.characters.count == 2 :
            self.textField3.becomeFirstResponder()

            // Jump backwards
        case self.textField3 where string.characters.count == 0 :
            self.textField2.becomeFirstResponder()
        case self.textField2 where string.characters.count == 0 :
            self.textField1.becomeFirstResponder()
        default :
            break
        }

        return true
    }
}
Seductive answered 15/7, 2016 at 17:30 Comment(6)
@JonSetting for back space you can check if the text box is empty then set the previous text box as firstResponserUnderclothes
This is on the right track, but the string is just the new input: it will have length 1. You need to check the location of the proposed range. The second part is pretty much just wrong: textField:shouldChangeCharactersInRange:replacementString: isn't called for a backspace at the beginning of a text field.Tow
This is a great solution and will help others. But I just realized I can't use it because the text alignment has to be center.Deary
Has anyone actually read or run this code?! It doesn't work as written. The idea is sound, but it's not implemented in this code. @JonSettingTow
Have you checked the instance member names? The class name? Have the appropriate delegates set?Seductive
This is not the correct implementation for the proposed solution.Deary
C
3

This can be achieve in one single textField as asked. I only tapped "1", in the gif.

enter image description here

  1. You select your keypad type to be number (0-9), which can ensure everything that will be input there is number only.

  2. Then you can adopt the textField delegate and implement the delegate method

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { print(string)

    let  char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
    let isBackSpace = { return strcmp(char, "\\b") == -92}
    
    if (textField.text?.characters.count == 3 && !isBackSpace()) || (textField.text?.characters.count == 6 && !isBackSpace()){
        textField.text = textField.text! + " "
    }
    
    if (textField.text?.characters.count) == 11 && !isBackSpace(){
        ssnString = textField.text!
        self.view.endEditing(true)
    
    }
    
    return true
    

    }

This includes the logic of adding space after third digit if you are not backspacing and same for the after 6th digit.

Also, after user input 11 digit, it will not allow user to input more number as the format of SSN, after 11 digit is input, the SSN is saved in ssnString, which will be used for you special masking.

  1. Because you don't want to mask space, we can not use secureTextEntry. So in the didEndEditing, I gave an condition only if the user enter the full SSN, we will mask it, which can be modified to any scenario if you want. But i think this makes more sense.

func textFieldDidEndEditing(textField: UITextField) { if textField.text?.characters.count == 11 { maskSSNTextField() } }

In the maskSSNTextField method, func maskSSNTextField() { textField.text = "••• •• ••••" }

  1. Finally, we need to unmask it when user come back to it, if they want to change the text

func textFieldDidBeginEditing(textField: UITextField) { if textField.text == "••• •• ••••"{ textField.text = ssnString } }

This fully fulfilled your requirement. Please let me know if you have other question.

Caveator answered 15/7, 2016 at 18:18 Comment(2)
But it's supposed to be a secured text field, I can see clearly the whole number you enter. That's not the purpose of secure text. Secure text just shows the last digit you entered for a very short time.Seductive
If you set it to be secured the space will be masked as well. You can manually do secure text in the didChangeInRange by replacing your second last digit character with "•", if string != " "Caveator
C
2

I changed the didChangeInRange method to meet your new requirement, although I think my previous answer could work. Now it works as in the gif. If you want it to be still masked, you can change the code in textField did begin editing.

enter image description here

   func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    let  char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
    let isBackSpace = { return strcmp(char, "\\b") == -92}

    if (textField.text?.characters.count == 3 && !isBackSpace()) || (textField.text?.characters.count == 6 && !isBackSpace()){
        textField.text = textField.text! + " "
        ssnString = ssnString + " "
    }



    if isBackSpace() {

        ssnString = ssnString.substringToIndex(ssnString.endIndex.predecessor())

    }else {

        ssnString = ssnString + string
        print(ssnString)

        if ssnString.characters.count >= 2 {
            var starString = ""
            for i in 0...ssnString.characters.count-2 {

                if i==3 || i==6 {
                    starString = starString+" "
                }else {
                    starString = starString+"•"
                }
            }
            textField.text = ""
            print(ssnString.characters.last)
            textField.text = starString
        }
    }

    if (textField.text?.characters.count) == 11 && !isBackSpace(){
        self.view.endEditing(true)

    }
    return true
}
Caveator answered 15/7, 2016 at 19:56 Comment(2)
Let me know if you have other query.Caveator
Do not post another answer, edit the one you have already wrote instead.Seductive
M
1

The simple solution I have been using is to convert my input string to an NSAttributedString with text spacing (.kern) attributes added at the proper locations and keeping isSecureTextEntry set to true. Disabling isSecureTextEntry and doing it by hand in addition of being overly complex could have security implications at least if someone is using a third party keyboard.

var ssnText = "123456789"

let spacerPositions = [ 2, 4 ]
let spacingAmount: CGFloat = 5.0

let spacerRanges:[NSRange] = spacerPositions
                                .filter { $0 < ssnText.count - 1 }
                                .map { NSRange(location: $0, length: 1) }

let attributedString = NSMutableAttributedString(string: ssnText)
for range in spacerRanges {
    attributedString.addAttribute(.kern, value: spacingAmount, range: range)
}

textField.attributedText = attributedString

calling that stuff in textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String).

Marin answered 17/9, 2019 at 17:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.