How to add a grouping separator while user is typing a number into UITextField? - swift

I am doing a currency type UITextField. The behavior I need is like this:
I managed to create the decimal part. But I have problems adding in the grouping separators for thousands. How do I group the integer part here?
Here is the code so far:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let DIGITS = ["0","1","2","3","4","5","6","7","8","9","0"]
let DECIMAL_SEPERATOR = ","
let THOUSAND_SEPERATOR = "."
let DECIMAL_DIGITS = 2
let DECIMAL_DIGITS_DEFAULT_STRING = "00"
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textField.text = "0\(DECIMAL_SEPERATOR)\(DECIMAL_DIGITS_DEFAULT_STRING)"
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Textfield
func textFieldTriggerDone(){
if !textField.text!.containsString(DECIMAL_SEPERATOR){
textField.text = textField.text! + DECIMAL_SEPERATOR + DECIMAL_DIGITS_DEFAULT_STRING
}
print("Add missing parts here")
}
func textFieldTriggerDecimalTyped(){
textField.text = textField.text! + DECIMAL_SEPERATOR
}
func textFieldShouldAddNumber(text: String, range: NSRange, replacement: String, dots: Int) -> Bool{
let nsstring = NSString(string: text)
let decimalRange = nsstring.rangeOfString(DECIMAL_SEPERATOR)
if range.location > decimalRange.location{
let parts = text.componentsSeparatedByString(DECIMAL_SEPERATOR)
if parts.count > 1{
if parts[1].characters.count > (DECIMAL_DIGITS-1){
return false
}
}
}else if range.location < decimalRange.location{
if textField.text!.characters.count == 1 && textField.text! == "0"{
textField.text = replacement
return false
}else{
let insertIndex = text.startIndex.advancedBy(range.location-dots)
var finalText = text
if replacement.characters.count > 0 {
finalText.insert(replacement.characters.first!, atIndex: insertIndex)
textField.text = finalText
let begin = textField.beginningOfDocument
let pos = textField.positionFromPosition(begin, offset: (range.location+1))
let cursorpos = textField.textRangeFromPosition(pos!, toPosition: pos!)
textField.selectedTextRange = cursorpos
//textFieldAddThousandSeperators()
return false
}
}
}
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
textFieldTriggerDone()
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let dots = textField.text!.occurancesOf(THOUSAND_SEPERATOR)
if string == ""{
if textField.text!.characters.count > 1{
return true
}else{
textField.text! = "0"
return false
}
}
if range.location == 0 && string == "0"{
return false
}
if DIGITS.contains(string){
return textFieldShouldAddNumber(textField.text!, range: range, replacement: string, dots: dots)
}
if string == DECIMAL_SEPERATOR || string == THOUSAND_SEPERATOR{
if textField.text!.containsString(DECIMAL_SEPERATOR){
return false
}
textFieldTriggerDecimalTyped()
return false
}
return false
}
}

Check out this answer:
https://stackoverflow.com/a/40469426/6863743
code:
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}
func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
To get it to convert back to something you can do calculations with, you can just write a function to take out the "," and "." and so on.
Hope this helps a bit, it's the best solution I've found for currency formatting.

Related

How to check if text is underlined

I am struggling to determine if some selected text in a UITextView is underlined. I can quite easily check for bold, italics etc with the following code:
let isItalic = textView.font!.fontDescriptor.symbolicTraits.contains(.traitItalic)
However, I can't figure out how to check for underline?
I have just created a sample project and I think you could do something like the following:
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let attrText1 = NSMutableAttributedString(string: "TestTest", attributes: [.foregroundColor : UIColor.systemTeal, .underlineStyle: NSUnderlineStyle.single.rawValue])
let attrText2 = NSAttributedString(string: " - not underlined", attributes: [.foregroundColor : UIColor.red])
attrText1.append(attrText2)
textView.attributedText = attrText1
}
func isTextUnderlined(attrText: NSAttributedString?, in range: NSRange) -> Bool {
guard let attrText = attrText else { return false }
var isUnderlined = false
attrText.enumerateAttributes(in: range, options: []) { (dict, range, value) in
if dict.keys.contains(.underlineStyle) {
isUnderlined = true
}
}
return isUnderlined
}
#IBAction func checkButtonDidTap(_ sender: UIButton) {
print(isTextUnderlined(attrText: textView.attributedText, in: textView.selectedRange))
}
}
Create an extension to get the selectedRange as NSRange:
extension UITextInput {
var selectedRange: NSRange? {
guard let range = selectedTextRange else { return nil }
let location = offset(from: beginningOfDocument, to: range.start)
let length = offset(from: range.start, to: range.end)
return NSRange(location: location, length: length)
}
}
I believe underline is not part of the font traits, it must rather be an attribute to the text. You might find the answer to this question useful. I hope it helps you! Enumerate over a Mutable Attributed String (Underline Button)
func checkForUnderline(){
let allWords = self.testView.text.split(separator: " ")
for word in allWords {
let result = self.isLabelFontUnderlined(textView: self.testView,
subString: word as NSString)
if(result == true){
print(word+" is underlined")
}else{
print(word+" is not underlined")
}
}
}
func isLabelFontUnderlined (textView: UITextView, subString:
NSString) -> Bool {
let nsRange = NSString(string: textView.text).range(of: subString as
String, options: String.CompareOptions.caseInsensitive)
if nsRange.location != NSNotFound {
return self.isLabelFontUnderlined(textView: textView,
forRange: nsRange)
}
return false
}
func isLabelFontUnderlined (textView: UITextView, forRange: NSRange) ->
Bool{
let attributedText = testView.attributedText!
var isRangeUnderline = false
attributedText.enumerateAttributes(in: forRange,
options:.longestEffectiveRangeNotRequired) { (dict, range, value) in
if dict.keys.contains(.underlineStyle) {
if (dict[.underlineStyle] as! Int == 1){
isRangeUnderline = true
} else{
isRangeUnderline = false
}
}else{
isRangeUnderline = false
}
}
return isRangeUnderline
}

Maximum number of characters as well as limiting only numeric values in UITextField

Is there a way to limit a UITextField to only numeric value as well as limiting the length.
I have the below two functions but don't know how I can use the shouldChangeCharactersIn twice in the same delegate. Any ideas how to use both of these
// Allow Numeric Only in Quantity
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowCharacters = ".-+1234567890"
let allowedCharacterSet = CharacterSet(charactersIn: allowCharacters)
let typedCharacterSet = CharacterSet(charactersIn: string)
return allowedCharacterSet.isSuperset(of: typedCharacterSet)
}
// Limit the length of the input
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldText = textField.text,
let rangeOfTextToReplace = Range(range, in: textFieldText) else {
return false
}
let substringToReplace = textFieldText[rangeOfTextToReplace]
let count = textFieldText.count - substringToReplace.count + string.count
return count <= 20
}
Many Thanks
You can do it like this
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldText = textField.text,
let rangeOfTextToReplace = Range(range, in: textFieldText) else {
return false
}
let substringToReplace = textFieldText[rangeOfTextToReplace]
let count = textFieldText.count - substringToReplace.count + string.count
let allowedCharacters = ".-+1234567890"
let allowedCharcterSet = CharacterSet(charactersIn: allowedCharacters)
let typedCharcterSet = CharacterSet(charactersIn: string)
if allowedCharcterSet.isSuperset(of: typedCharcterSet)
, count <= 20 {
return true
} else {
return false
}
}
Or you can use this Subclass of UITextField
class JDTextField: UITextField {
#IBInspectable var maxLength: Int = 0 // Max character length
var valueType: ValueType = ValueType.none // Allowed characters
/************* Added new feature ***********************/
// Accept only given character in string, this is case sensitive
#IBInspectable var allowedCharInString: String = ""
func verifyFields(shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
switch valueType {
case .none:
break // Do nothing
case .onlyLetters:
let characterSet = CharacterSet.letters
if string.rangeOfCharacter(from: characterSet.inverted) != nil {
return false
}
case .onlyNumbers:
let numberSet = CharacterSet.decimalDigits
if string.rangeOfCharacter(from: numberSet.inverted) != nil {
return false
}
case .phoneNumber:
let phoneNumberSet = CharacterSet(charactersIn: "+0123456789")
if string.rangeOfCharacter(from: phoneNumberSet.inverted) != nil {
return false
}
case .alphaNumeric:
let alphaNumericSet = CharacterSet.alphanumerics
if string.rangeOfCharacter(from: alphaNumericSet.inverted) != nil {
return false
}
case .fullName:
var characterSet = CharacterSet.letters
print(characterSet)
characterSet = characterSet.union(CharacterSet(charactersIn: " "))
if string.rangeOfCharacter(from: characterSet.inverted) != nil {
return false
}
}
if let text = self.text, let textRange = Range(range, in: text) {
let finalText = text.replacingCharacters(in: textRange, with: string)
if maxLength > 0, maxLength < finalText.utf8.count {
return false
}
}
// Check supported custom characters
if !self.allowedCharInString.isEmpty {
let customSet = CharacterSet(charactersIn: self.allowedCharInString)
if string.rangeOfCharacter(from: customSet.inverted) != nil {
return false
}
}
return true
}
}
How to use it
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Verify all the conditions
if let sdcTextField = textField as? JDTextField {
return sdcTextField.verifyFields(shouldChangeCharactersIn: range, replacementString: string)
} else {
return true
}
}
And in viewDidLoad() you set characteristics in just one line
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.textField.delegate = self
self.textField.maxLength = 10
self.textField.allowedCharInString = ".-+1234567890"
}

Cursor shifts to end on edit of formatted decimal textfield - Swift

I am formatting a UITextField such that it becomes comma separated (Decimal style) while typing.
So 12345678 becomes 12,345,678
Now when I edit the UITextField, say I want to remove 5, at that time I tap after 5 and delete it but the cursor shifts to the end of the text immediately, that's after 8.
Here's my code which I have used to format the decimal while typing:
func checkTextField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if ((string == "0" || string == "") && (textField.text! as NSString).range(of: ".").location < range.location) {
return true
}
let allowedCharacterSet = NSCharacterSet(charactersIn: "0123456789.").inverted
let filtered = string.components(separatedBy: allowedCharacterSet)
let component = filtered.joined(separator: "")
let isNumeric = string == component
if isNumeric {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let numberWithOutCommas = newString.replacingOccurrences(of: ",", with: "")
let number = formatter.number(from: numberWithOutCommas)
if number != nil {
var formattedString = formatter.string(from: number!)
if string == "." && range.location == textField.text?.count {
formattedString = formattedString?.appending(".")
}
textField.text = formattedString
} else {
textField.text = nil
}
}
return false
}
It is called like this:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let textFieldText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let checkForCurrency = checkTextField(textField, shouldChangeCharactersIn: range, replacementString: string)
return checkForCurrency
}
I have tried the following but in vain :
Solution 1
Solution 2
What could the reason for cursor shift be?
It should be something like in this link
Any help would be appreciated!
update your custom function with :
func checkTextField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let textFieldText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
if ((string == "0" || string == "") && (textField.text! as NSString).range(of: ".").location < range.location) {
return true
}
var currentPosition = 0
if let selectedRange = textField.selectedTextRange {
currentPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
}
let allowedCharacterSet = NSCharacterSet(charactersIn: "0123456789.").inverted
let filtered = string.components(separatedBy: allowedCharacterSet)
let component = filtered.joined(separator: "")
let isNumeric = string == component
if isNumeric {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let numberWithOutCommas = newString.replacingOccurrences(of: ",", with: "")
let number = formatter.number(from: numberWithOutCommas)
if number != nil {
var formattedString = formatter.string(from: number!)
if string == "." && range.location == textField.text?.count {
formattedString = formattedString?.appending(".")
}
textField.text = formattedString
if(textFieldText.count < formattedString?.count ?? 0){
currentPosition = currentPosition + 1
}
}else{
textField.text = nil
}
}
if(string == ""){
currentPosition = currentPosition - 1
}else{
currentPosition = currentPosition + 1
}
if let newPosition = textField.position(from: textField.beginningOfDocument, offset: currentPosition) {
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
return false
}

How to format currency in text field [duplicate]

I have a number let’s say 0.00.
When the user taps 1. We should have 0.01
When the user taps 2. We should display 0.12
When the user taps 3. We should display 1.23
When the user taps 4. We should display 12.34
How can I do that with Swift?
For Swift 3. Input currency format on a text field (from right to left)
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}
#objc func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
You can create a currency text field subclassing UITextField. Add a target for UIControlEvents .editingChanged. Add a selector method to filter the digits from your textfield string. After filtering all non digits from your string you can format again your number using NumberFormatter as follow:
Xcode 11.5 • Swift 5.2 or later
import UIKit
class CurrencyField: UITextField {
var decimal: Decimal { string.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
var maximum: Decimal = 999_999_999.99
private var lastValue: String?
var locale: Locale = .current {
didSet {
Formatter.currency.locale = locale
sendActions(for: .editingChanged)
}
}
override func willMove(toSuperview newSuperview: UIView?) {
// you can make it a fixed locale currency if needed
// self.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
Formatter.currency.locale = locale
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
keyboardType = .numberPad
textAlignment = .right
sendActions(for: .editingChanged)
}
override func deleteBackward() {
text = string.digits.dropLast().string
// manually send the editingChanged event
sendActions(for: .editingChanged)
}
#objc func editingChanged() {
guard decimal <= maximum else {
text = lastValue
return
}
text = decimal.currency
lastValue = text
}
}
extension CurrencyField {
var doubleValue: Double { (decimal as NSDecimalNumber).doubleValue }
}
extension UITextField {
var string: String { text ?? "" }
}
extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
private extension Formatter {
static let currency: NumberFormatter = .init(numberStyle: .currency)
}
extension StringProtocol where Self: RangeReplaceableCollection {
var digits: Self { filter (\.isWholeNumber) }
}
extension String {
var decimal: Decimal { Decimal(string: digits) ?? 0 }
}
extension Decimal {
var currency: String { Formatter.currency.string(for: self) ?? "" }
}
extension LosslessStringConvertible {
var string: String { .init(self) }
}
View Controller
class ViewController: UIViewController {
#IBOutlet weak var currencyField: CurrencyField!
override func viewDidLoad() {
super.viewDidLoad()
currencyField.addTarget(self, action: #selector(currencyFieldChanged), for: .editingChanged)
currencyField.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
}
#objc func currencyFieldChanged() {
print("currencyField:",currencyField.text!)
print("decimal:", currencyField.decimal)
print("doubleValue:",(currencyField.decimal as NSDecimalNumber).doubleValue, terminator: "\n\n")
}
}
Sample project
SwiftUI version of this post here
I started with Leo Dabus' answer (which didn't work out of the box for me) and in the process of trying to simplify and make it work ended up with this, which I think is pretty lean & clean if I do say so myself 😎
class CurrencyTextField: UITextField {
/// The numbers that have been entered in the text field
private var enteredNumbers = ""
private var didBackspace = false
var locale: Locale = .current
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
override func deleteBackward() {
enteredNumbers = String(enteredNumbers.dropLast())
text = enteredNumbers.asCurrency(locale: locale)
// Call super so that the .editingChanged event gets fired, but we need to handle it differently, so we set the `didBackspace` flag first
didBackspace = true
super.deleteBackward()
}
#objc func editingChanged() {
defer {
didBackspace = false
text = enteredNumbers.asCurrency(locale: locale)
}
guard didBackspace == false else { return }
if let lastEnteredCharacter = text?.last, lastEnteredCharacter.isNumber {
enteredNumbers.append(lastEnteredCharacter)
}
}
}
private extension Formatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter
}()
}
private extension String {
func asCurrency(locale: Locale) -> String? {
Formatter.currency.locale = locale
if self.isEmpty {
return Formatter.currency.string(from: NSNumber(value: 0))
} else {
return Formatter.currency.string(from: NSNumber(value: (Double(self) ?? 0) / 100))
}
}
}
Try this piece of code:
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(s:String) {
if count(fraction) < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if count(fraction) == 0 { return "00" }
if count(fraction) == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
val.stringVal
val.enter("2")
val.stringVal
val.enter("3")
val.stringVal
val.enter("4")
val.stringVal
My final code thanks for your help
extension Double {
var twoDigits: Double {
let nf = NSNumberFormatter()
nf.numberStyle = NSNumberFormatterStyle.DecimalStyle
nf.minimumFractionDigits = 2
nf.maximumFractionDigits = 2
return self
}
}
var cleanText:String!
let number:String = sender.currentTitle as String!
if(amountDisplay.text != nil)
{
cleanText = String(Array(amountDisplay.text!).map{String($0)}.filter{ $0.toInt() != nil }.map{Character($0)} ) as String
cleanText = cleanText + number
}else{
cleanText = number
}
amount = (Double(cleanText.toInt()!) / 100).twoDigits
formatter.locale = NSLocale(localeIdentifier: currencies[current_currency_index])
amountDisplay.text = "\(formatter.stringFromNumber(amount!)!)"
Here is a code for swift 2
#IBOutlet weak var txtAmount: UITextField!
//MARK: - UITextField Delegate -
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if string.characters.count == 0 {
return true
}
let userEnteredString = textField.text ?? ""
var newString = (userEnteredString as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString
newString = newString.stringByReplacingOccurrencesOfString(".", withString: "")
let centAmount : NSInteger = newString.integerValue
let amount = (Double(centAmount) / 100.0)
if newString.length < 16 {
let str = String(format: "%0.2f", arguments: [amount])
txtAmount.text = str
}
return false //return false for exact out put
}
Note : Connect delegate for textField from storyboard or programatically
Just for fun: copied Thomas's answer (full credits -and points- to him please) into a file to run as a Swift 4.1 script (with minor fixes):
dotnum.swift:
#!/usr/bin/swift
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(_ s:String) {
if fraction.count < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if fraction.count == 0 { return "00" }
if fraction.count == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
print(val.stringVal)
val.enter("2")
print(val.stringVal)
val.enter("3")
print(val.stringVal)
val.enter("4")
print(val.stringVal)
Then run it in a terminal:
$ chmod +x dotnum.swift
$ ./dotnum.swift
0.01
0.21
3.21
43.21
Thanks to everyone here. From all the answers here I managed to come out with mine.
First I set up the initial value of the textField to be:
private func commonInit() {
amountTextField.text = "0.00"
}
Then I use the UITextFieldDelegate to get the input value and the current textview.text:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Need to check if the textfield.text can be evaluated as number or not before passing it to the function
//Get the current text value, and current user input and pass it to the
let formattedAmount = formatAmount(oldAmount: textField.text, userInput: string)
textField.text = formattedAmount
return false
}
Here go my private function to format the number to move from right to left:
private func formatAmount(currentText: String, userInput: String) -> String {
let amount = currentText.components(separatedBy: ".")
var intValue: String = amount[0]
var decimalValue: String = amount[1]
//backspace registered, need to move the number to the right
if userInput.isEmpty {
decimalValue.remove(at: decimalValue.index(before: decimalValue.endIndex))
decimalValue = intValue.last!.string + decimalValue
intValue.remove(at: intValue.index(before: intValue.endIndex))
if intValue.isEmpty {
intValue = "0"
}
} else {
//Need to consider if user paste value
if userInput.count > 2 {
decimalValue = String(userInput.suffix(2))
intValue = String(userInput.dropLast(2))
} else {
decimalValue = rmAmount[1] + userInput
//Add to int value (move to the right)
intValue = intValue + decimalValue.first!.string
if Int(intValue) == 0 {
intValue = "0" //00 -> 0
} else if intValue.first == "0" {
//remove 0 from at the first position in intValue
intValue.remove(at: intValue.startIndex) //01 -> 1
}
//Remove tenth place from decimal value since it goes to Int already
decimalValue.remove(at: decimalValue.startIndex)
}
}
return intValue + "." + decimalValue
}
This is basically it. Other extra implementations can be added by your own initiatives. Let me know if there is any problem with my implementation.
PS: This is of course only works for certain currency only, in my case, my apps is set up only for that local so thats why I use this way.
After a lot of trial and error with the suggested answers, I found a pretty straight forward solution:
The setup for the textField needs to be called in your view's setup.
In the switch statement, if the user puts in a number between 0 and 9, the number is added to the previous string value. The default case covers the backspace button and removes the last character from the string.
The locale for the numberFormatter is set to current, so it works with different currencies.
func setupTextField() {
textField.delegate = self
textField.tintColor = .clear
textField.keyboardType = .numberPad
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
setFormattedAmount(string)
return false
}
private func setFormattedAmount(_ string: String) {
switch string {
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
amountString = amountString + string
default:
if amountString.count > 0 {
amountString.removeLast()
}
}
let amount = (NSString(string: amountString).doubleValue) / 100
textField.text = formatAmount(amount)
}
private func formatAmount(_ amount: Double) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
if let amount = formatter.string(from: NSNumber(value: amount)) {
return amount
}
return ""
}

function to find all NSRanges for a string in string in swift

I would like to have all CG letters pairs in red color in DNA sequences (string). I can do it by using NSMutableAttributedString.
For this I need NSRanges of all CG positions and I have tried to make the function below but in the line
indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn)
rangeOfString with range cannot be invoked with an argument list of type (String, options: Int, range: NSRange). Where is a mistake ? What i did wrong ?
func searchForNSRangesOfStringInString(stringOut: String, stringIn: String) -> [NSRange]
{
var arrayOfindexesOfStringIn = [NSRange]()
if stringIn.characters.count > 1
{
var rangeNS = (stringOut as NSString).rangeOfString(stringIn)
if rangeNS.location != NSNotFound
{
let endIndexOfStringOut = (stringOut as NSString).length - 1
var indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn)
arrayOfindexesOfStringIn = [indexesOfStringIn]
var lastIndexOfStringIn = (stringOut as NSString).rangeOfString(stringIn).location
var rangeForIndexStringIn = NSRange(lastIndexOfStringIn...endIndexOfStringOut)
while indexesOfStringIn.location != NSNotFound
{
indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn)
if indexesOfStringIn.location != NSNotFound
{
arrayOfindexesOfStringIn.append(indexesOfStringIn)
lastIndexOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn).location
rangeForIndexStringIn = NSRange(lastIndexOfStringIn...endIndexOfStringOut)
}
}
}
}
return arrayOfindexesOfStringIn
}
As suggested by uchuugaka it is much easier to use NSRegularExpression to return an array of the matches ranges in a string as follow:
extension String {
func findOccurencesOf(text text:String) -> [NSRange] {
return !text.isEmpty ? try! NSRegularExpression(pattern: text, options: []).matchesInString(self, options: [], range: NSRange(0..<characters.count)).map{ $0.range } : []
}
}
let str = "CGCGCGCGCG"
let ranges = str.findOccurencesOf(text: "CG")
print(ranges.count) // 5
Just add a control event for your textField Editing changed and loop through the ranges as follow:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: "coloring:", forControlEvents: UIControlEvents.EditingChanged)
textField.becomeFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func coloring(sender:UITextField) {
let attText = NSMutableAttributedString(string: sender.text ?? "")
let ranges = sender.text!.uppercaseString.findOccurencesOf(text: "CG")
for range in ranges {
attText.addAttributes([NSForegroundColorAttributeName : UIColor.redColor()], range: range)
}
sender.attributedText = attText
}
}
extension String {
func findOccurencesOf(text text:String) -> [NSRange] {
return !text.isEmpty ? try! NSRegularExpression(pattern: text, options: []).matchesInString(self, options: [], range: NSRange(0..<characters.count)).map{ $0.range } : []
}
}