1

I have a textfield where user can input only 6 decimals, if there are decimals, if there aren't then he is allowed to input as many characters as user wants.

For example, I am allowing this: 7472828282 and this: 0,123456, not this: 0,2139213773219312.

And my current implementation is ok with this examples above and when user is making input from ekeyboard, but I can't manage to make it work when user pastes some value, for example user can paste this value: 0,123456789, but I would like to cut it after 6th decimal, to be actually like this: 0,123456, and no, I don't need to round it on bigger decimal, I need to cut it!

Thanks for help, my so far code is below

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField === self.amountView.textField {
    
    guard let text = textField.text, let decimalSeparator = NSLocale.current.decimalSeparator else {
        return true
    }
    
    var splitText = text.components(separatedBy: decimalSeparator)
    let totalDecimalSeparators = splitText.count - 1
    let isEditingEnd = (text.count - 3) < range.lowerBound
    
    splitText.removeFirst()
    
    if  splitText.last?.count ?? 0 > 5 && string.count != 0 && isEditingEnd {
        return false
    }
    
    if totalDecimalSeparators > 0 && string == decimalSeparator {
        return false
    }
}
return true
}
miki
  • 75
  • 9

2 Answers2

2

This code always updates the content of textField but limits the number of decimals:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    guard let decimalSeparator = NSLocale.current.decimalSeparator else {return true}

    // Updates the text
    var updatedText = (textView.text as NSString).replacingCharacters(in: range, with: text) 

    // If someone needs to cover all possible decimal separator values, the commented line below is the solution
    // let textComponents = updatedText.components(separatedBy: [",", "."])

    let textComponents = updatedText.components(separatedBy: decimalSeparator)
    
    // Truncates the decimals
    if textComponents.count > 1 && textComponents[1].count > 6{
       updatedText = textComponents[0].appending(decimalSeparator).appending((textComponents[1] as NSString).substring(to: 6))
    }
        
    textView.text = updatedText

    // The text has already been updated, so returns false
    return false 
}
Alain Bianchini
  • 3,883
  • 1
  • 7
  • 27
  • Answers are more helpful (to the author of the question and to future readers of this thread) if they *explain* the problem and the solution, instead of posting code only. – Martin R Nov 17 '21 at 12:52
  • Where is the `String` function `appending(_:)` documented? I can't find it anywhere, and even the "Jump to definition" function in Xcode fails to show me the definition in the Foundation headers. – Duncan C Nov 17 '21 at 13:22
  • 1
    thank you @RedWheelbarrow, your comment partially works, but when I try to delete my input, error I get is this: Thread 1: "*** -[__NSCFString substringToIndex:]: Index 6 out of bounds; string length 5" – miki Nov 17 '21 at 13:24
  • 1
    @miki I have corrected my answer – Alain Bianchini Nov 17 '21 at 13:25
  • @DuncanC `appending(_:)` is defined in Foundation.NSString – Alain Bianchini Nov 17 '21 at 13:29
  • one more question @RedWheelbarrow, how and where should I implement, if the text field is empty and the user clicks on the decimal separator to add "0," in textfield? just like bank apps have similar things – miki Nov 17 '21 at 13:33
  • actually, I discovered new problem with your answer @RedWheelbarrow, since decimal separator is declared as locale separator (which in my case is ","), when I copy/paste input as: 0.4123424 your solutio isn't working, since I am checking only locale separator. thanks! – miki Nov 17 '21 at 13:40
  • @miki Thanks for your edit that covers all cases. If you want to set the content of the textField to `"0"` when the user taps it if its content is empty, you can simply add a `UITapGestureRecognizer` to the textField, then in the action you can check if the text is empty and set it to the desired value (pay attention to eventual conflicts with the normal behavior of the textField). If the user need expressively to tap the decimal separator, you need to check the tapped character like in this [question](https://stackoverflow.com/questions/18704692/detect-character-tapped-in-uitextview) – Alain Bianchini Nov 17 '21 at 14:01
1

Your mistake: You are checking the old content.

shouldChangeCharacters gives you the old text (still in the text field), a range to be replaced (selection or just insertion point) and replacement. You first need to calculate the new text if the range was replaced, and then check that result.

gnasher729
  • 51,477
  • 5
  • 75
  • 98