Skip to content

Instantly share code, notes, and snippets.

@ZalyalovIldar
Forked from andrealufino/UITextField+Mask.swift
Last active January 23, 2024 17:44
Show Gist options
  • Save ZalyalovIldar/7588bb2758bb871fe0c036e1ece3fc8e to your computer and use it in GitHub Desktop.
Save ZalyalovIldar/7588bb2758bb871fe0c036e1ece3fc8e to your computer and use it in GitHub Desktop.
Adding Mask to a TextField | Swift 4, GCD | + Static Prefix if need.
/// Usage: phoneTextField.formatPattern(pattern: "(***) ***-**-**", replacementChar: "*", staticPrefixString: "+7 ", allowText: false, allowNumbers: true)
extension UITextField {
/// Структура с перечислениями не хранимых полей
private struct FieldMaskProperties {
static var pattern = "pattern"
static var replacementChar = "replacementChar"
static var staticPrefixString = "staticPrefixString"
static var allowNumbers = "allowNumbers"
static var allowText = "allowText"
}
/// Pattern you want to use Example: (***)**-**-**
private var pattern: String {
if let result = objc_getAssociatedObject(self, &FieldMaskProperties.pattern) as? String {
return result
}
return ""
}
/// Symbol to change in pattern (ex: "*" or "X")
private var replacementChar: String {
if let result = objc_getAssociatedObject(self, &FieldMaskProperties.replacementChar) as? String {
return result
}
return ""
}
/// Is text allowed or not
private var allowText: Bool {
if let result = objc_getAssociatedObject(self, &FieldMaskProperties.allowText) as? Bool {
return result
}
return true
}
/// Is numbers allowed or not
private var allowNumbers: Bool {
if let result = objc_getAssociatedObject(self, &FieldMaskProperties.allowNumbers) as? Bool {
return result
}
return true
}
/// Unchangable prefix in pattern (прим.: "+0")
private var staticPrefixString: String {
if let result = objc_getAssociatedObject(self, &FieldMaskProperties.staticPrefixString) as? String {
return result
}
return ""
}
/// Create pattern format for text
///
/// - Warning: Use deinit for stop receiving notifications from field
func formatPattern(pattern: String? = nil, replacementChar: String? = nil, staticPrefixString: String? = nil, allowText: Bool = true, allowNumbers: Bool = true)
{
objc_setAssociatedObject(self, &FieldMaskProperties.pattern, pattern ?? "", .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &FieldMaskProperties.replacementChar, replacementChar ?? "*", .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &FieldMaskProperties.staticPrefixString, staticPrefixString ?? "", .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &FieldMaskProperties.allowNumbers, allowNumbers, .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(self, &FieldMaskProperties.allowText, allowText, .OBJC_ASSOCIATION_RETAIN)
registerNotifications()
}
private func registerNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange), name: NSNotification.Name(rawValue: "UITextFieldTextDidChangeNotification"), object: self)
}
func removeNotifications() {
NotificationCenter.default.removeObserver(self)
}
private 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
}
var tempString = string
if !staticPrefixString.isEmpty {
tempString = String(tempString[staticPrefixString.endIndex...])
}
let result = tempString.components(separatedBy: charSet)
return staticPrefixString + result.joined(separator: "")
}
@objc func textDidChange() {
guard let text = self.text else {
return
}
guard !text.isEmpty, !pattern.isEmpty else { return }
var finalText = ""
var stop = false
let tempString = prepareString(text)
var formatIndex = pattern.startIndex
var tempIndex = staticPrefixString.isEmpty ? tempString.startIndex : staticPrefixString.endIndex
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
guard let strongSelf = self else { return }
while !stop
{
let formattingPatternRange = formatIndex ..< strongSelf.pattern.index(formatIndex, offsetBy: 1)
if strongSelf.pattern[formattingPatternRange.lowerBound..<formattingPatternRange.upperBound] != String(strongSelf.replacementChar) {
finalText = finalText.appending(strongSelf.pattern[formattingPatternRange.lowerBound..<formattingPatternRange.upperBound])
} else if tempString.count > 0 {
let pureStringRange = tempIndex ..< tempString.index(tempIndex, offsetBy: 1)
finalText = finalText.appending(tempString[pureStringRange.lowerBound..<pureStringRange.upperBound])
tempIndex = tempString.index(tempIndex, offsetBy: 1)
}
formatIndex = strongSelf.pattern.index(formatIndex, offsetBy: 1)
if formatIndex >= strongSelf.pattern.endIndex || tempIndex >= tempString.endIndex {
stop = true
}
DispatchQueue.main.async { [weak self] in
self?.text = finalText
}
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment