Forked from andrealufino/UITextField+Mask.swift
Last active
January 23, 2024 17:44
-
-
Save ZalyalovIldar/7588bb2758bb871fe0c036e1ece3fc8e to your computer and use it in GitHub Desktop.
Adding Mask to a TextField | Swift 4, GCD | + Static Prefix if need.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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