user7065798
user7065798

Reputation: 103

Swift. Format input in textField

I created textField and want to format text in it like 43/35. Number / number - for credit card month and year.

Can I use number fomatter for it or how can I do it more easily? The issue here that I need to replace 3rd character if I add new character and remove it if I remove 2nd one.

I do not want use any 3rd party library, I need native implementation

Upvotes: 2

Views: 9040

Answers (2)

J A S K I E R
J A S K I E R

Reputation: 2204

Swift 4:

extension CodeEnterViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let normalText = textField.text else { return false }

        let beginning = textField.beginningOfDocument
        // save cursor location
        let cursorLocation = textField.position(from: beginning, offset: range.location + string.count)

        let newString = (normalText as NSString).replacingCharacters(in: range, with: string)
        let newStringClean = newString.stringWithOnlyNumbers().withMask(mask: mask)

        guard newString != newStringClean else { return true }

        textField.text = newStringClean
        guard string != "" else { return false }

        // fix cursor location after changing textfield.text
        if let cL = cursorLocation {
            let textRange = textField.textRange(from: cL, to: cL)
            textField.selectedTextRange = textRange
        }

        return false
    }
}

String

extension String {
    func stringWithOnlyNumbers() -> String {
        return self.reduce("") { (acc, c) -> String in
            guard c.isDigit() else { return acc }
            return "\(acc)\(c)"
        }
    }

    func withMask(mask: String) -> String {
        var resultString = String()

        let chars = self
        let maskChars = mask

        var stringIndex = chars.startIndex
        var maskIndex = mask.startIndex

        while stringIndex < chars.endIndex && maskIndex < maskChars.endIndex {
            if (maskChars[maskIndex] == "#") {
                resultString.append(chars[stringIndex])
                stringIndex = chars.index(after: stringIndex)
            } else {
                resultString.append(maskChars[maskIndex])
            }
            maskIndex = chars.index(after: maskIndex)
        }

        return resultString
    }

}

Character

extension Character {
    func isDigit() -> Bool {
        let s = String(self).unicodeScalars
        let uni = s[s.startIndex]

        let digits = NSCharacterSet.decimalDigits
        let isADigit = digits.hasMember(inPlane: UInt8(uni.value))

        return isADigit
    } }

Upvotes: 0

ghashi
ghashi

Reputation: 1557

This is my current solution. Basically you need to:

1) Implement the delegate of your textfield somewhere (in my code below I implemented on the ViewController)

2) Implement textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool.

To apply the mask, I created some extensions for String and Characters, as you can see at the end of the following code:

class ViewController: UIViewController {
    @IBOutlet weak var textfield: UITextField!

    let mask = "##/##"

    override func viewDidLoad() {
        super.viewDidLoad()
        textfield.delegate = self
    }
}


extension ViewController: UITextFieldDelegate {
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        guard let normalText = textField.text else { return false }

        let beginning = textField.beginningOfDocument
        // save cursor location
        let cursorLocation = textField.positionFromPosition(beginning, offset: range.location + string.characters.count)

        let newString = (normalText as NSString).stringByReplacingCharactersInRange(range, withString: string)
        let newStringClean = newString.stringWithOnlyNumbers().withMask(mask)

        guard newString != newStringClean else { return true }

        textField.text = newStringClean
        guard string != "" else { return false } 

        // fix cursor location after changing textfield.text
        if let cL = cursorLocation {
            let textRange = textField.textRangeFromPosition(cL, toPosition: cL)
            textField.selectedTextRange = textRange
        }

        return false
    }
}

extension String {
    func stringWithOnlyNumbers() -> String {
        return self.characters.reduce("") { (acc, c) -> String in
            guard c.isDigit() else { return acc }
            return "\(acc)\(c)"
        }
    }

    func withMask(mask: String) -> String {
        var resultString = String()

        let chars = self.characters
        let maskChars = mask.characters

        var stringIndex = chars.startIndex
        var maskIndex = mask.startIndex

        while stringIndex < chars.endIndex && maskIndex < maskChars.endIndex {
            if (maskChars[maskIndex] == "#") {
                resultString.append(chars[stringIndex])
                stringIndex = stringIndex.successor()
            } else {
                resultString.append(maskChars[maskIndex])
            }
            maskIndex = maskIndex.successor()
        }

        return resultString
    }

}

extension Character {
    func isDigit() -> Bool {
        let s = String(self).unicodeScalars
        let uni = s[s.startIndex]

        let digits = NSCharacterSet.decimalDigitCharacterSet()
        let isADigit = digits.longCharacterIsMember(uni.value)

        return isADigit
    }
}

Upvotes: 5

Related Questions