Skip to content

Instantly share code, notes, and snippets.

@dedeexe
Last active November 16, 2018 21:14
Show Gist options
  • Save dedeexe/13589e0a69efd228da33705fdc1415cf to your computer and use it in GitHub Desktop.
Save dedeexe/13589e0a69efd228da33705fdc1415cf to your computer and use it in GitHub Desktop.
Adding Mask to a TextField
//
// UITextField+MaskPattern.swift
// MaskedTextField
//
// Created by dede.exe on 10/07/16.
//
// It's totally base on article : "http://vojtastavik.com/2015/03/29/real-time-formatting-in-uitextfield-swift-basics/"
// And I don't mind if the original author allow me to publish it :)... But I'll keep the reference
// I modified the original implementation to keep it as an Extension...
//
extension UITextField {
private struct FKFieldPatterns {
static var pattern = "pattern"
static var replacementChar = "replacementChar"
static var allowNumbers = "allowNumbers"
static var allowText = "allowText"
}
private var pattern : String {
if let result = objc_getAssociatedObject(self, &FKFieldPatterns.pattern) as? String {
return result
}
return ""
}
private var replacementChar : String {
if let result = objc_getAssociatedObject(self, &FKFieldPatterns.replacementChar) as? String {
return result
}
return ""
}
private var allowText : Bool {
if let result = objc_getAssociatedObject(self, &FKFieldPatterns.allowText) as? Bool {
return result
}
return true
}
private var allowNumbers : Bool {
if let result = objc_getAssociatedObject(self, &FKFieldPatterns.allowNumbers) as? Bool {
return result
}
return true
}
func formatPattern(pattern:String?=nil, replacementChar:String?=nil, allowText:Bool=true, allowNumbers:Bool=true)
{
objc_setAssociatedObject(self, &FKFieldPatterns.pattern, pattern ?? "", .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &FKFieldPatterns.replacementChar, replacementChar ?? "*", .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &FKFieldPatterns.allowNumbers, allowNumbers, .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &FKFieldPatterns.allowText, allowText, .OBJC_ASSOCIATION_RETAIN)
registerNotifications()
}
private func registerNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(textDidChange), name: "UITextFieldTextDidChangeNotification", object: self)
}
private func removeNotifications() {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func prepareString(string:String) -> String
{
var charSet : NSCharacterSet!
if allowText && allowNumbers {
charSet = NSCharacterSet.alphanumericCharacterSet().invertedSet
}
else if allowText {
charSet = NSCharacterSet.letterCharacterSet().invertedSet
}
else if allowNumbers {
charSet = NSCharacterSet.decimalDigitCharacterSet().invertedSet
}
let result = string.componentsSeparatedByCharactersInSet(charSet)
return result.joinWithSeparator("")
}
func textDidChange(notification : NSNotification) {
guard let text = self.text else {
return
}
if text.characters.count > 0 && pattern.characters.count > 0
{
var finalText = ""
var stop = false
let tempString = prepareString(text)
var formatIndex = pattern.startIndex
var tempIndex = tempString.startIndex
while !stop
{
let formattingPatternRange = formatIndex ..< formatIndex.advancedBy(1)
if pattern.substringWithRange(formattingPatternRange) != String(replacementChar) {
finalText = finalText.stringByAppendingString(pattern.substringWithRange(formattingPatternRange))
}
else if tempString.characters.count > 0 {
let pureStringRange = tempIndex ..< tempIndex.advancedBy(1)
finalText = finalText.stringByAppendingString(tempString.substringWithRange(pureStringRange))
tempIndex = tempIndex.advancedBy(1)
}
formatIndex = formatIndex.advancedBy(1)
if formatIndex >= pattern.endIndex || tempIndex >= tempString.endIndex {
stop = true
}
self.text = finalText
}
}
}
}
//=========================================================
//= EXAMPLE =
//=========================================================
var creditCardTextField : UITextField!
var dateTextField : UITextField!
...
cardNumberTextField.formatPattern("**** **** **** ****", replacementChar:"*", allowText: false, allowNumbers: true)
dateTextfield.formatPattern("**/**/****", replacementChar:"*", allowText:false, allowNumbers: true)
@JoaoPauloZ
Copy link

JoaoPauloZ commented Nov 16, 2018

// You should move the "self.text = finalText" out of the loop. This way it will not call ui runloop to refresh the screen.
// Updated to Swift 4

// https://gist.github.com/dedeexe/13589e0a69efd228da33705fdc1415cf
// UITextField+MaskPattern.swift
// MaskedTextField
//
// Created by dede.exe on 10/07/16.
// Modified by João Paulo Serodio on 11/16/18.
//
// It's totally base on article : "http://vojtastavik.com/2015/03/29/real-time-formatting-in-uitextfield-swift-basics/"
// And I don't mind if the original author allow me to publish it :)... But I'll keep the reference
// I modified the original implementation to keep it as an Extension...
//

extension UITextField {

private struct FKFieldPatterns {
    static var pattern          = "pattern"
    static var replacementChar  = "replacementChar"
    static var allowNumbers     = "allowNumbers"
    static var allowText        = "allowText"
}

private var pattern: String {
    if let result = objc_getAssociatedObject(self, &FKFieldPatterns.pattern) as? String {
        return result
    }
    return ""
}

private var replacementChar: String {
    if let result = objc_getAssociatedObject(self, &FKFieldPatterns.replacementChar) as? String {
        return result
    }
    return ""
}

private var allowText: Bool {
    if let result = objc_getAssociatedObject(self, &FKFieldPatterns.allowText) as? Bool {
        return result
    }
    return true
}

private var allowNumbers: Bool {
    if let result = objc_getAssociatedObject(self, &FKFieldPatterns.allowNumbers) as? Bool {
        return result
    }
    return true
}

func format(pattern: String? = nil, replacementChar: String? = nil,
            allowText: Bool = true, allowNumbers: Bool = true) {

    objc_setAssociatedObject(self, &FKFieldPatterns.pattern, pattern ?? "", .OBJC_ASSOCIATION_RETAIN)
    objc_setAssociatedObject(self, &FKFieldPatterns.replacementChar, replacementChar ?? "*",
                             .OBJC_ASSOCIATION_RETAIN)
    objc_setAssociatedObject(self, &FKFieldPatterns.allowNumbers, allowNumbers, .OBJC_ASSOCIATION_RETAIN)
    objc_setAssociatedObject(self, &FKFieldPatterns.allowText, allowText, .OBJC_ASSOCIATION_RETAIN)
    registerNotifications()
}

private func registerNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(textDidChange),
                                           name: NSNotification.Name(
                                           rawValue: "UITextFieldTextDidChangeNotification"),
                                           object: self)
}

private func removeNotifications() {
    NotificationCenter.default.removeObserver(self)
}

func prepareString(string: String) -> String {
    var charSet: CharacterSet!

    if allowText && allowNumbers {
        charSet = CharacterSet.alphanumerics.inverted
    } else if allowText {
        charSet = CharacterSet.letters.inverted
    } else if allowNumbers {
        charSet = CharacterSet.decimalDigits.inverted
    }

    let result = string.components(separatedBy: charSet)
    return result.joined(separator: "")
}

@objc func textDidChange(notification: NSNotification) {

    guard let text = self.text else {
        return
    }

    if text.count > 0 && pattern.count > 0 {
        var finalText   = ""
        var stop        = false
        let tempString  = prepareString(string: text)

        var formatIndex = pattern.startIndex
        var tempIndex   = tempString.startIndex

        while !stop {
            let formattingPatternRange = formatIndex ..< pattern.index(formatIndex, offsetBy: 1)

            if pattern[formattingPatternRange] != String(replacementChar) {
                finalText = finalText.appending(pattern[formattingPatternRange])
            } else if tempString.count > 0 {
                let pureStringRange = tempIndex ..< tempString.index(tempIndex, offsetBy: 1)
                finalText = finalText.appending(tempString[pureStringRange])
                tempIndex = tempString.index(tempIndex, offsetBy: 1)
            }

            formatIndex = pattern.index(formatIndex, offsetBy: 1)

            if formatIndex >= pattern.endIndex || tempIndex >= tempString.endIndex {
                stop = true
            }
        }
        self.text = finalText
    }
}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment