Eminem
Eminem

Reputation: 13

Swift 5 - Mask Textfield Text with X

What I want to implement is, TextField format should be XXXX XXXX XXXX when the user clicks on the show button it should show the text 1234 5678 9123

Below is my code where I was able to implement the 1234 5678 9123 format, masking and storing original data is pending.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
        if textField.tag == 0 || textField.tag == 1 {
        if string.isBackspace
        {
            if range.location == 5 || range.location == 10
            {
                textField.text?.removeLast()
            }
        }
        
        if string == ""{
            return true
        }

        //range.length will be greater than 0 if user is deleting text - allow it to replace
        if range.length > 0
        {
            return true
        }

        //Don't allow empty strings
        if string == " "
        {
            return false
        }

        //Check for max length including the spacers we added
        if range.location > 13 //23
        {
            return false
        }

        var originalText = textField.text
        let replacementText = string.replacingOccurrences(of: " ", with: "")

        //Verify entered text is a numeric value
        let digits = NSCharacterSet.decimalDigits
        for char in replacementText.unicodeScalars
        {
            if !(digits as NSCharacterSet).longCharacterIsMember(char.value)
            {
                return false
            }
        }
        //Put an empty space after every 4 places
        if (originalText?.count)! > 0
        {
            if (originalText?.count)! < 5 && (originalText?.count)! % 4 == 0{
                originalText?.append(" ")
            }else if(((originalText?.count)! + 1) % 5 == 0){
                originalText?.append(" ")
            }

        }

        textField.text = originalText
        }
        
        //2 - Nick Name,3 - Recipient Name
        if textField.tag == 2 || textField.tag == 3 {
            //Max Limit for Nick Name and Benificiary Name is 65
            if range.location > 64
            {
                return false
            }
        }
        return true
    }

Upvotes: 1

Views: 3041

Answers (2)

Eminem
Eminem

Reputation: 13

Finally i am able to solve this, Here is the Solution by Adwait Barkale. Any Queries Mail me at [email protected]

NOTE:- arrPersonalDetails[2].value is just a String(use a variable where you want to store the original text entered)

1.UITextfield Delegate

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
    {
        if textField.tag == 0 || textField.tag == 1 {
            //For Masking Numbers 1234 5678 9123 into XXXX XXXX XXXX
            //1.Storing original data 1234 5678 9123 into arrPersonalDetails[index].value
            //2.Formatting textfield by putting spaces after every 4 and 8 digit
            
            //Step 1 Store Data Locally
            if string.isBackspace
            {
                if range.location == 5 || range.location == 10
                {
                    textField.text?.removeLast()
                }
                if arrPersonalDetails[textField.tag].value!.count == 11 || arrPersonalDetails[textField.tag].value!.count == 6
                {
                    arrPersonalDetails[textField.tag].value!.removeLast()
                }
            }
            //Check for max length including the spacers we added
            if range.location > 13 //23
            {
                return false
            }
            
            if string.isBackspace
            {
                if arrPersonalDetails[textField.tag].value!.count != 0 && arrPersonalDetails[textField.tag].value!.count != 11 || arrPersonalDetails[textField.tag].value!.count != 0 && arrPersonalDetails[textField.tag].value!.count != 6
                {
                    let truncated = String(arrPersonalDetails[textField.tag].value!.dropLast())
                    arrPersonalDetails[textField.tag].value = truncated
                }
            } else {
                if arrPersonalDetails[textField.tag].value!.count == 4 || arrPersonalDetails[textField.tag].value!.count == 9
                {
                    arrPersonalDetails[textField.tag].value!.append(" ")
                }
                arrPersonalDetails[textField.tag].value!.append(string)
            }
            let oValue = arrPersonalDetails[textField.tag].value
            arrPersonalDetails[textField.tag].value = oValue
            
            if string == ""{
                return true
            }
            
            //range.length will be greater than 0 if user is deleting text - allow it to replace
            if range.length > 0
            {
                return true
            }
            
            //Don't allow empty strings
            if string == " "
            {
                return false
            }
            
            //Step 2 - Manage Textfield Data
            var originalText = textField.text
            let replacementText = string.replacingOccurrences(of: " ", with: "")
            
            //Verify entered text is a numeric value
            let digits = NSCharacterSet.decimalDigits
            for char in replacementText.unicodeScalars
            {
                if !(digits as NSCharacterSet).longCharacterIsMember(char.value)
                {
                    return false
                }
            }
            //Put an empty space after every 4 places
            if (originalText?.count)! > 0
            {
                if (originalText?.count)! < 5 && (originalText?.count)! % 4 == 0{
                    originalText?.append(" ")
                }else if(((originalText?.count)! + 1) % 5 == 0){
                    originalText?.append(" ")
                }
                
            }
            textField.text = originalText
        }
        return true
    }

2.Add Target to Textfield

cell.tflData.addTarget(self, action: #selector(maskTextfield(tfl:)), for: .editingChanged)

/// Function to mask unmask textfiel's text
    /// - Parameter tfl: UITextfield
    @objc func maskTextfield(tfl: UITextField)
    {
        let value = arrPersonalDetails[tfl.tag].value
        if showOriginalText //Change this value on your button click
        {
            tfl.text = value
        } else {
            //Masking numbers with X
            var maskedText = ""
            if value!.count != 0 {
            for i in 1...value!.count {
                if i == 5 || i == 10 {
                    maskedText.append(" ")
                } else {
                    maskedText.append("X")
                }
            }
            }
            print("Masked Text = \(maskedText)")
            tfl.text = maskedText
        }
    }

Edit:-

Note :- In Textfield did end editing don't write someVariable = textField.text other wise it will take XXXX XXXX XXXX

instead write this,

func textFieldDidEndEditing(_ textField: UITextField) {
        //Bank Account textfield
        if textField.tag == 2
        {
            self.arrPersonalDetails[2].value = self.arrPersonalDetails[2].value
        }
        //Re-enter Bank Account textfield
        if textField.tag == 3
        {
            self.arrPersonalDetails[3].value = self.arrPersonalDetails[3].value
        }
    }

Happy Coding!!!

Upvotes: 0

Matic Oblak
Matic Oblak

Reputation: 16774

If I understand correctly your requirement is to create a custom secure text field. Requirements are:

  • User may enter up to 12 symbols
  • Only numbers are allowed
  • Text field may have "masked" and "normal" mode
  • Text displayed should be formatted by grouping 4 characters together
  • There is a space between each group
  • In "masked" mode all numbers are replaced with an "X" symbol
  • In normal mode numbers are visible

In addition it would be nice that all text field functionality would work, including copy-paste, selecting, inserting code in middle of text...

I created a new project and quickly ended with this:

class ViewController: UIViewController {
    
    @IBOutlet private var textField: UITextField?
    
    private var actualText: String = ""
    private var maskedText: String = ""
    
    private var masked: Bool = true {
        didSet {
            refreshTextField()
        }
    }
    
    @IBAction private func maskedTogglePressed(_ sender: Any) {
        masked = !masked
    }
    
    private func refreshTextField() {
        textField?.text = masked ? maskedText : actualText
    }
}

extension ViewController: UITextFieldDelegate {
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        var newString = (actualText as NSString).replacingCharacters(in: range, with: string) // Apply new text
        
        // Remove all whitespaces
        newString = newString.replacingOccurrences(of: " ", with: "")
        // Remove all that is not a number
        newString = newString.filter("0123456789".contains)
        
        // Split string into components of max 4
        var components: [String] = {
            var toDeplete = newString
            var components: [String] = []
            while !toDeplete.isEmpty {
                let length = min(toDeplete.count, 4)
                components.append(String(toDeplete.prefix(length)))
                toDeplete.removeFirst(length)
            }
            return components
        }()
        // Limit to maximum of 3 components
        if components.count > 3 {
            components = Array(components[0..<3])
        }
        
        // Generate masked components, replacing all characters with X
        let maskedComponents: [String] = components.map { String($0.map { character in return "X" }) }
        
        // Add spaces
        newString = components.joined(separator: " ")
        let maskedText = maskedComponents.joined(separator: " ")
        
        // Assign new strings
        self.actualText = newString
        self.maskedText = maskedText
        
        // Refresh field
        refreshTextField()
        
        // Disallow text field to apply it's change
        return false
    }
    
}

Could use some improvements when dealing with strings. But it does the job.

Code is commented and should give more information about the solution.

Upvotes: 1

Related Questions