Skip to content

Instantly share code, notes, and snippets.

@nickkohrn
Last active July 15, 2017 16:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickkohrn/6b8884d6fd706e61cb9048efac186ca8 to your computer and use it in GitHub Desktop.
Save nickkohrn/6b8884d6fd706e61cb9048efac186ca8 to your computer and use it in GitHub Desktop.
This gist provides code for handling appropriate live-formatting of currency input for the current locale with respect to the placement of the currency's symbol.
internal final class CurrencyService {
// MARK: - Currency Symbol Position
internal static var currencySymbolPosition: CurrencySymbolPosition {
let currencyFormat = CFNumberFormatterGetFormat(CFNumberFormatterCreate(nil, Locale.current as CFLocale, .currencyStyle)) as NSString
let positiveNumberFormat = currencyFormat.components(separatedBy: ";")[0] as NSString
let currencySymbolLocation = positiveNumberFormat.range(of: "¤").location
let position: CurrencySymbolPosition = currencySymbolLocation == 0 ? .before : .after
return position
}
// MARK: - CurrencySymbolPosition
internal enum CurrencySymbolPosition {
case before
case after
}
}
internal final class HorizontallyExpandableTextField: UITextField {
// MARK: - Properties
private var previousText: String?
internal override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
if isEditing, let lastTextBeforeEditing = previousText {
let originalSize = (lastTextBeforeEditing as NSString).size(attributes: typingAttributes)
size.width = size.width - originalSize.width
}
return size
}
// MARK: - Initialization
internal override init(frame: CGRect) {
super.init(frame: frame)
setupTextChangeNotification()
}
internal required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupTextChangeNotification()
}
deinit { NotificationCenter.default.removeObserver(self) }
// MARK: - Notification Handling
internal func setupTextChangeNotification() {
NotificationCenter.default.addObserver(forName: .UITextFieldTextDidChange, object: self, queue: OperationQueue.main) { (notification) in
self.invalidateIntrinsicContentSize()
}
NotificationCenter.default.addObserver(forName: .UITextFieldTextDidBeginEditing, object: self, queue: OperationQueue.main) { (notification) in
self.previousText = self.text
}
}
}
internal func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.characters.count == 0 && range.length >= 1 { return true }
guard let text = textField.text else { return false }
guard let currencySymbol = formatter.currencySymbol else { return false }
guard let decimalSeparator = formatter.currencyDecimalSeparator else { return false }
guard let groupingSeparator = formatter.currencyGroupingSeparator else { return false }
guard let newText = (text as NSString?)?.replacingCharacters(in: range, with: string) else { return false }
let symbolsStripped = newText.replacingOccurrences(of: currencySymbol, with: "").replacingOccurrences(of: decimalSeparator, with: "").replacingOccurrences(of: groupingSeparator, with: "")
guard let integer = Int(symbolsStripped) else { return false }
let divisor = Int(pow(Double(10), Double(formatter.maximumFractionDigits)))
let double = Double(integer) / Double(divisor)
guard let formatted = formatter.string(from: double as NSNumber) else { return false }
textField.text = formatted.replacingOccurrences(of: currencySymbol, with: "").trimmingCharacters(in: .whitespaces)
return false
}
@nickkohrn
Copy link
Author

This code is useful for handling formatted currency input for the current locale. It takes into account the location of the currency's symbol so that the currency can be live-formatted upon input without having to worry about disabling the ability to delete the currency symbol in the text input view.

For example, if you place an UILabel on either side of an UITextField, you can determine the position of the currency's symbol and set the appropriate label to contain the symbol. You can then set the text of the unused label to nil. This will display the currency symbol and the currency's value appropriately.

Coupled with an instance of HorizontallyExpandableTextField, the trailing label can be configured to be pinned to the trailing edge of the text field. This will allow the label to keep an explicit distance from the trailing edge of the text field for currencies that display the symbol after the value.

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