Editing Decimal Numbers with UITextField control with MonoTouch

Posted by ESCOZ on Saturday, January 16, 2010

Here’s another control I created while developing an iphone app for a client: UIDecimalField. The entire source code can be found on github, together with the other controls I have created so far. You can see the control in use in the image on the right.

The new control inherits from the UITextField control, and allows the user to edit decimal values using the NumberPad keyboard, instead of the normal keyboard. Again, as I mentioned in my previous post, inheritance provides a much better way of adding functionality to existing UIKit controls, instead of doing composition.

First, we need to change the Keyboard type used by the control. That is as simple as changing the property during the initialization of the control:

public partial class UIDecimalField : UITextField
{
    public decimal Value {
        get { return UIDecimalField.GetAmountFromString(Text); }
        set { Text = value.ToString("N2"); }
    }

    public UIDecimalField (IntPtr ptr) : base(ptr) {
        Initialize();
    }

    protected void Initialize() {
        KeyboardType = UIKeyboardType.NumberPad;
        Delegate = new UIDecimalFieldDelegate();
    }
}

Now we need to handle the user input and transform the text in the control, so that numbers are always formatted as a decimals, including rounding and decimal/thousand separators. That is done by creating a new class that inherits from UITextFieldDelegate, and overriding the ShouldChangeCharacters() method, as below:

public partial class UIDecimalField : UITextField
{
    public decimal Value {
        get { return UIDecimalField.GetAmountFromString(Text); }
        set { Text = value.ToString("N2"); }
    }

    public UIDecimalField (Decimal currentValue): base() {
        Value = currentValue;
        Initialize();
    }

    public UIDecimalField (IntPtr ptr) : base(ptr) {
        Initialize();
    }

    protected void Initialize() {
        KeyboardType = UIKeyboardType.NumberPad;
        Delegate = new UIDecimalFieldDelegate();
    }

    private class UIDecimalFieldDelegate : UITextFieldDelegate {
        public override bool ShouldChangeCharacters (UITextField textField, 
            NSRange range, string replacementString) {
            
            var newText = textField.Text.Remove(range.Location, range.Length)
                            .Insert(range.Location, replacementString);

            if (newText.Length>0){
                textField.Text = (UIDecimalField.GetAmountFromString(newText)).ToString("N2");
            }
            return false;
        }
    }
}

Overriding this method means that we’ll have to rewrite the way a normal UITextField handles user input. That is done using the Insert()/Remove() methods in the first line. With the new text, we’ll have to convert the value to a decimal and reformat it, which is done by the static method listed below. Finally, we return false to prevent the base class from handling the user input.

Here’s the static method to convert the text:

private static decimal GetAmountFromString(string text){
    if (text.Length==0)
        return 0;

    var cleanedUpText = "";
    foreach (char c in text)
        if (Char.IsDigit(c)) cleanedUpText+=c;

    return (decimal.Parse(cleanedUpText))/100;
}

The code above rounds the decimals to 2 decimal places; modifying the code for different rounding should be just a matter of adding an additional property in the class and modifying the last row of the method above and the ToString(“N2”) above.